COMP6991 Supplementary Exam

Getting Started

Create a new directory for this lab called exam_supp, change to this directory, and fetch the provided code for the exam by running these commands:

mkdir exam_supp
cd exam_supp
6991 fetch exam

Or, if you're not working on CSE, you can download the provided code as a tar file.

Exercise:
Exam Preamble

Starting time: 2026-01-07 14:00:00

Finishing time: 2026-01-07 17:00:00

Time for the exam: 3 hours

This exam contains 7 questions, each of equal weight (10 marks each).

Total number of marks: 70

Total number of practical programming questions: 3 (30 marks)

Total number of theoretical programming questions: 4 (40 marks)

You should attempt all questions.

Exam Condition Summary

  • This exam is "Open Book"
  • Joint work is NOT permitted in this exam
  • You are NOT permitted to communicate (email, phone, message, talk) with anyone during this exam, except for the COMP6991 staff via cs6991.exam@cse.unsw.edu.au
  • The exam paper is confidential, sharing it during or after the exam is prohibited.
  • You are NOT permitted to submit code that is not your own
  • You may NOT ask for help from online sources.
  • Even after you finish the exam, on the day of the exam, do NOT communicate your exam answers to anyone. Some students have extended time to complete the exam.
  • Do NOT place your exam work in any location, including file sharing services such as Dropbox or GitHub, accessible to any other person.
  • Your zpass should NOT be disclosed to any other person. If you have disclosed your zpass, you should change it immediately.
  • The use of AI assistants is strictly prohibited in this exam. This includes services such as Github Copilot, OpenAI ChatGPT, agentic coding tools, etc.
Deliberate violation of these exam conditions will be referred to Student Integrity Unit as serious misconduct, which may result in penalties up to and including a mark of 0 in COMP6991 and exclusion from UNSW.
  • You are allowed to use any resources from the course during the exam.
  • You are allowed to use small amounts of code (< 10 lines) of general-purpose code (not specific to the exam) obtained from a site such as Stack Overflow or other publicly available resources. You should attribute the source of this code clearly in an accompanying comment.

Exam submissions will be checked, both automatically and manually, for any occurrences of plagiarism.

By starting this exam, as a student of The University of New South Wales, you do solemnly and sincerely declare that you have not seen any part of this specific examination paper for the above course prior to attempting this exam, nor have any details of the exam's contents been communicated to you. In addition, you will not disclose to any University student any information contained in the abovementioned exam for a period of 24 hrs after the exam. Violation of this agreement is considered Academic Misconduct and penalties may apply.

For more information, read the UNSW Student Code, or contact the Course Account.

  • This exam comes with starter files.
  • You will be able to commence the exam and fetch the files once the exam commences.
  • You may complete the exam questions using any platform you wish (VLab, VSCode, etc). You should ensure that the platform works correctly.
  • You may submit your answers, using the give command provided below each question.
  • You can use give to submit as many times as you wish. Only the last submission will be marked.
  • Do NOT leave it to the deadline to submit your answers. Submit each question when you finish working on it.
  • Please make sure that you submit all your answers at the conclusion of the exam - running the autotests does not automatically submit your code.
  • Autotests are available for all practical questions to assist in your testing. You can use the command: 6991 autotest
  • Passing autotests does not guarantee any marks. Remember to do your own testing!
  • No marks are awarded for commenting - but you can leave comments for the marker to make your code more legible as needed

Language Restriction

  • All practical programming questions must be answered entirely in Rust; you may not submit code in any other programming languages.
  • You are not permitted to use third-party crates other than the standard library (std).

Fit to Sit

By sitting or submitting an assessment on the scheduled assessment date, a student is declaring that they are fit to do so and cannot later apply for Special Consideration.

If, during an exam a student feels unwell to the point that they cannot continue with the exam, they should take the following steps:

  1. Stop working on the exam and take note of the time
  2. Contact us immediately, using cs6991.exam@cse.unsw.edu.au, and advise us that you are unwell
  3. Immediately submit a Special Consideration application saying that you felt ill during the exam and were unable to continue
  4. If you were able to advise us of the illness during the assessment (as above), attach screenshots of this conversation to the Special Consideration application

Technical Issues

If you experience a technical issue, you should take the following steps:

  1. If your issue is with the connection to CSE, please follow the following steps:
    • If you are using VLab: Try exiting VLAB and reconnecting again - this may put you on a different server, which may improve your connection. If you are still experiencing problems, you can try changing how you connect to the CSE servers. Consider:
    • If you are using VSCode remote-ssh: Try disconnecting VSCode, and then changing the URL from vscode.unsw.edu.au to vscode2.unsw.edu.au.
    • If you are using SSH: Try disconnecting SSH and reconnecting again.
  2. If things are still NOT working, take screenshots of as many of the following as possible:
    • error messages
    • screen not loading
    • timestamped speed tests
    • power outage maps
  3. Contact should be made immediately to advise us of the issue at cs6991.exam@cse.unsw.edu.au
  4. A Special Consideration application should be submitted immediately after the conclusion of the assessment, along with the appropriate screenshots.

Exercise:
Q1: Theory (10 marks)

Q1.1 (2 marks)

This question will ask you to comment on the following Rust snippet, which does not compile.

fn main() {
    let mut x = 5;
    let y = &x;
    x += 1;
    println!("{y}");
}

Question:

  1. Identify and briefly explain which feature of the Rust language is preventing this code from compiling. (1 mark)
  2. Suggest one way to fix this code so it compiles successfully. (1 mark)

Write your answer in exam_q1/q1_1.txt.

When you are finished working on your answer, submit your work with give:

give cs6991 exam_q1_1 q1_1.txt


Q1.2 (4 marks)

This question will ask you to comment on the following Rust snippet, which compiles correctly.

fn main() {
    let value = "42"; // let 1

    let value = value.parse::<i32>().unwrap(); // let 2

    let value = value * 2; // let 3

    println!("{value}");
}

Question:

  1. Identify the type of value after let 1. (1 mark)
  2. Identify the type of value after let 2. (1 mark)
  3. Identify the Rust feature that allows the same variable name to be reused across multiple let statements. (1 mark)
  4. Briefly argue one advantage or one disadvantage of this feature. (1 mark)

Write your answer in exam_q1/q1_2.txt.

When you are finished working on your answer, submit your work with give:

give cs6991 exam_q1_2 q1_2.txt


Q1.3 (4 marks)

Here is a function that retrieves an element from an array, written in both C and Rust.

// C version
int get_element(int index) {
    int numbers[3] = {1, 2, 3};
    return numbers[index];
}

// Rust version
fn get_element(index: usize) -> i32 {
    let numbers = [1, 2, 3];
    numbers[index]
}

Question:

  1. Explain what happens if get_element(10) is called in the C version. (1 mark)
  2. Explain what happens if get_element(10) is called in the Rust version. (1 mark)
  3. Explain the philosophy behind Rust's behaviour compared to C in this situation. (1 mark)
  4. Identify one potential disadvantage of Rust's approach. (1 mark)

Write your answer in exam_q1/q1_3.txt.

When you are finished working on your answer, submit your work with give:

give cs6991 exam_q1_3 q1_3.txt


Exercise:
Q2: Practical (10 marks)

You are building a search system for a digital library. The system needs to find text within books and return search results containing both the search term and the surrounding context.

We have written three items which are implemented correctly, but are missing lifetime annotations. Your task is to add the correct lifetime annotations.

use require_lifetimes::require_lifetimes;

/// Returns the line from `book` that contains `query`, if any.
/// (3 marks)
#[require_lifetimes]
pub fn get_context(book: &str, query: &str) -> Option<&str> {
    for line in book.lines() {
        if line.contains(query) {
            return Some(line);
        }
    }
    None
}

/// A search result containing the search term and its surrounding context.
/// (2 marks)
pub struct SearchResult {
    pub query: &str,
    pub context: &str,
}

/// Searches for `query` across all books in the library.
/// Returns a SearchResult for each line containing the query.
/// (5 marks)
#[require_lifetimes]
pub fn search_library(library: &Vec<&str>, query: &str) -> Vec<SearchResult> {
    let mut results = vec![];
    for book in library {
        for line in book.lines() {
            if line.contains(query) {
                results.push(SearchResult {
                    query,
                    context: line,
                });
            }
        }
    }
    results
}

You must add lifetime annotations to:

  • get_context: a function that finds a line containing the query (3 marks)
  • SearchResult: a struct holding the search term and its context (2 marks)
  • search_library: a function that searches multiple books (5 marks)

You are only permitted to add, remove, or modify lifetime annotations. You must not change any other code in src/lib.rs.

This is an example of the expected behaviour:

6991 cargo run --bin exam_q2
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/exam_q2`
Found context: Rust is great for systems programming
Search results:
  query: 'the', context: 'Once upon the time'
  query: 'the', context: 'In the beginning'

6991 cargo run --bin exam_q2_alt
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/exam_q2_alt`
Context: Goodbye world
Found 3 lazy lines:
  'lazy' in: Jumps over the lazy dog
  'lazy' in: A lazy afternoon
  'lazy' in: With lazy clouds

Write your answer in exam_q2/src/lib.rs.

When you think your program is working, you can use autotest to run some simple automated tests:

6991 autotest

When you are finished working on your answer, submit your work with give:

cd src/
give cs6991 exam_q2 lib.rs


Exercise:
Q3: Theory (10 marks)

Q3.1 (4 marks)

Consider the following (simplified) definition of the Iterator trait from the standard library:

trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    
    fn count(self) -> usize where Self: Sized {
        let mut count = 0;
        while self.next().is_some() {
            count += 1;
        }
        count
    }
}

Question:

  1. Identify what must be provided in order to implement Iterator for a custom type. (2 marks)
  2. Explain how it is possible for count to have an implementation inside the trait definition. (1 mark)
  3. Identify any trait bounds required by fn count. (1 mark)

Write your answer in exam_q3/q3_1.txt.

When you are finished working on your answer, submit your work with give:

give cs6991 exam_q3_1 q3_1.txt


Q3.2 (3 marks)

The following function does not compile:

fn process<T>(items: Vec<T>) -> Option<T>
where
    T: Clone
{
    Some(items.iter().max()?.clone())
}

Question:

  1. Identify the missing trait bound causing the compilation error. (2 marks)
  2. The function body can be rewritten to remove the Clone bound. Modify the function body such that the bound is no longer required. (1 mark)

Write your answer in exam_q3/q3_2.txt.

When you are finished working on your answer, submit your work with give:

give cs6991 exam_q3_2 q3_2.txt


Q3.3 (3 marks)

Consider the following code that creates a collection of plugins:

trait Plugin {
    fn name(&self) -> &str;
    fn execute(&self);
}

struct Logger;
struct Validator;

impl Plugin for Logger {
    fn name(&self) -> &str { "Logger" }
    fn execute(&self) { println!("Logging..."); }
}

impl Plugin for Validator {
    fn name(&self) -> &str { "Validator" }
    fn execute(&self) { println!("Validating..."); }
}

fn main() {
    let plugins: Vec<Box<dyn Plugin>> = vec![
        Box::new(Logger),
        Box::new(Validator),
    ];
    
    for plugin in &plugins {
        println!("{}: ", plugin.name());
        plugin.execute();
    }
}

Question:

  1. Explain why Box is required here rather than Vec<dyn Plugin> directly. (1 mark)
  2. Describe the limitation you would encounter if you used Vec<T> where T: Plugin instead of Vec<Box<dyn Plugin>>. Furthermore, explain why this limitation exists. (2 marks)

Write your answer in exam_q3/q3_3.txt.

When you are finished working on your answer, submit your work with give:

give cs6991 exam_q3_3 q3_3.txt


Exercise:
Q4: Theory (10 marks)

Q4.1 (4 marks)

Consider the following code that increments a shared counter across multiple threads:

use std::sync::{Arc, Mutex};
use std::thread;

fn main() {
    let counter = Arc::new(Mutex::new(0));

    let handles: Vec<_> = (0..10).map(|_| {
        let counter = Arc::clone(&counter);
        thread::spawn(move || {
            *counter.lock().unwrap() += 1;
        })
    }).collect();

    for handle in handles {
        handle.join().unwrap();
    }

    println!("Final: {}", *counter.lock().unwrap());
}

Question:

  1. The Mutex outlives all spawned threads due to the join() calls. Explain why Arc is still required in this code. (1 mark)
  2. Knowing the Mutex outlives the spawned threads, identify an alternative approach that would avoid the need for Arc, and explain why that approach works without Arc. (2 marks)
  3. Explain what would happen, and why, if Rc was used instead of Arc. (1 mark)

Write your answer in exam_q4/q4_1.txt.

When you are finished working on your answer, submit your work with give:

give cs6991 exam_q4_1 q4_1.txt


Q4.2 (3 marks)

The following code attempts to spawn a thread that uses some data from the main thread:

use std::thread;

fn main() {
    let data = vec![1, 2, 3];
    
    let handle = thread::spawn(|| {
        println!("{data:?}");
    });
    
    handle.join().unwrap();
}

However, this code does not compile.

Question:

  1. Explain why this code does not compile. (1 mark)
  2. Adding the move keyword before || fixes the compilation error. Explain what move does to fix the issue. (1 mark)
  3. If the programmer needed to use data in the main thread after spawning (using thread::spawn with a move closure), describe how they could modify the code to allow this. An answer containing only the correctly modified code is sufficient. (1 mark)

Write your answer in exam_q4/q4_2.txt.

When you are finished working on your answer, submit your work with give:

give cs6991 exam_q4_2 q4_2.txt


Q4.3 (3 marks)

You are tasked with implementing a repeat_vec! macro that creates a Vec with elements repeated a specified number of times.

The macro should use the following syntax, where each element is preceded by the number of times to repeat it and "of":

let v = repeat_vec![3 of 1, 2 of 2];
// v is vec![1, 1, 1, 2, 2]

let greetings = repeat_vec![2 of "hello", 1 of "world"];
// greetings is vec!["hello", "hello", "world"]

let single = repeat_vec![5 of 0];
// single is vec![0, 0, 0, 0, 0]

Question:

Implement the repeat_vec! macro. Provide the full macro_rules! declaration.

You will receive (1 mark) for correctly implementing the macro with only single element support (i.e. repeat_vec![N of expr]),

or (3 marks) for correctly implementing the macro supporting multiple elements with repetition.

Partial marks may be awarded for progress made.


Write your answer in exam_q4/q4_3.txt.

When you are finished working on your answer, submit your work with give:

give cs6991 exam_q4_3 q4_3.txt


Exercise:
Q5: Practical (10 marks)

Alex is building a data processing library called Processor. It wraps a Vec and provides convenient methods for transforming, filtering, and sorting data.

Alex started by making the system work only with String items. However, users want to process other types too! Alex needs to make the struct and methods generic to accommodate this.

It's late, so Alex has asked you to help finish the implementation. All that's needed is to make it as generic as possible!

You have been given the following starter code in src/lib.rs:

use std::collections::HashSet;

pub struct Processor {
    items: Vec<String>,
}

impl Processor {
    pub fn new(items: Vec<String>) -> Self {
        Self { items }
    }

    pub fn map_all(&self, f: fn(&String) -> String) -> Vec<String> {
        self.items.iter().map(f).collect()
    }

    pub fn filter_dedup(&self, pred: fn(&String) -> bool) -> Vec<String> {
        let mut seen = HashSet::new();
        self.items
            .iter()
            .filter(|x| pred(x) && seen.insert((*x).clone()))
            .cloned()
            .collect()
    }

    pub fn into_sorted(self, f: fn(Vec<String>) -> Vec<String>) -> Vec<String> {
        let mut result = f(self.items);
        result.sort();
        result
    }
}

This code correctly implements Processor for String items.

Your task is to make this struct and its methods far more generic. You will need to make the following changes:

  • Support items of any type, not just String.
  • Support mapping to any output type, not just String.
  • Convert the function pointers to appropriate closure types.

You will be required to constrain some generic type parameters as you solve the exercise. You must ensure that you do not overly constrain the types, only requiring what is minimally needed.

The code already runs correctly on a basic test case:

6991 cargo run --bin exam_q5
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/exam_q5`
Mapped: ["HELLO", "WORLD"]
Filtered unique: ["hello", "world"]
Sorted: ["goodbye", "hello", "world"]

However, the code does not yet typecheck on the other test cases provided. Once you have modified the types to make the Processor as generic as possible, you should find these other test cases now compile and run:

6991 cargo run --bin exam_q5_alt
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/exam_q5_alt`
Mapped: [1, 4, 9, 16, 25]
Filtered unique: [2, 4] (predicate called 7 times)
Sorted: [1, 2, 3, 4, 5, 100, 200]
6991 cargo run --bin exam_q5_alt2
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/exam_q5_alt2`
Mapped: [2, 4, 6]
Filtered unique: [Filterable(2), Filterable(3)]
Sorted: [Sortable(1), Sortable(2), Sortable(3)]
6991 cargo run --bin exam_q5_alt3
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/exam_q5_alt3`
Mapped: [2, 8, 24]
Filtered unique: [1, 2]
Dropped: FnOnce consumed
Sorted: [1, 2, 3, 4, 5]

Partial marks will be awarded for solutions which pass a subset of the test cases.


Write your answer in exam_q5/src/lib.rs.

When you think your program is working, you can use autotest to run some simple automated tests:

6991 autotest

When you are finished working on your answer, submit your work with give:

cd src/
give cs6991 exam_q5 lib.rs


Exercise:
Q6: Theory (10 marks)

Q6.1 (4 marks)

When writing unsafe Rust code, programmers take on additional responsibilities that are normally handled by the compiler.

Question:

  1. Explain what it means for unsafe code to be "sound". (2 marks)
  2. If a bug exists in an unsafe block, can that bug affect code outside of the unsafe block? Discuss. (2 marks)

Write your answer in exam_q6/q6_1.txt.

When you are finished working on your answer, submit your work with give:

give cs6991 exam_q6_1 q6_1.txt


Q6.2 (6 marks)

The following code attempts to write an implementation of a Cell<T>, making use of unsafe code.

use std::cell::UnsafeCell;

pub struct Cell<T> {
    value: UnsafeCell<T>,
}

impl<T> Cell<T> {
    pub fn new(value: T) -> Self {
        Self {
            value: UnsafeCell::new(value),
        }
    }

    pub fn set(&self, value: T) {
        unsafe {
            *self.value.get() = value;
        }
    }

    pub fn replace(&self, value: T) -> T {
        unsafe {
            std::mem::replace(&mut *self.value.get(), value)
        }
    }

    pub fn get(&self) -> T
    where
        T: Copy,
    {
        unsafe { *self.value.get() }
    }

    pub fn get_ref(&self) -> &T {
        unsafe { &*self.value.get() }
    }

    pub fn get_mut(&mut self) -> &mut T {
        self.value.get_mut()
    }
}

Cell provides interior mutability for Copy types, allowing mutation through a shared reference. It is commonly used for simple values like counters or flags that need to be modified even when only a shared reference is available.

The following example code aims to demonstrate how this type can be used:

use unsafe_review::Cell;

fn main() {
    let mut cell = Cell::new(10);
    println!("Initial: {}", cell.get());

    cell.set(20);
    println!("After set: {}", cell.get());

    let old = cell.replace(30);
    println!("After replace: {}, got back: {old}", cell.get());

    let r = cell.get_ref();
    println!("Via get_ref: {r}");

    let m = cell.get_mut();
    *m = 99;
    println!("Via get_mut: {}", cell.get());
}

When executed, the program outputs:

Initial: 10
After set: 20
After replace: 30, got back: 20
Via get_ref: 30
Via get_mut: 99

The implementation appears to work correctly for this simple example.

Question:

  1. Perform a brief code review on the Cell<T> implementation, with respect to the idiomatic usage of unsafe Rust. Soundness should not be considered, in this part. (1 mark)
  2. There exists a subtle unsoundness in Cell<T>, allowing a user to provoke a segmentation fault in safe Rust. Explain the soundness issue. (3 marks)
  3. Write an example fn main that exploits the unsoundness in Cell<T> to provoke a segmentation fault in safe Rust. (2 marks)
    An answer causing memory corruption other than a segmentation fault will be awarded only one mark.


Write your answer in exam_q6/q6_2.txt.

When you are finished working on your answer, submit your work with give:

give cs6991 exam_q6_2 q6_2.txt


Exercise:
Q7: Practical (10 marks)

StackLang Interpreter

In this question, you will implement an interpreter for StackLang, a simple stack-based programming language.

Stack-based languages operate on a stack of values. Operations pop values from the stack, perform computation, and push results back. This is also known as Reverse Polish Notation (RPN) or postfix notation.

For example, to compute 3 + 4 in StackLang, you write 3 4 +:

Step Token Action Stack
1 3 Push 3 [3]
2 4 Push 4 [3, 4]
3 + Pop 4 and 3, push 7 [7]

Input Format

Your program reads a single line from standard input containing a StackLang program. Tokens are separated by whitespace.

Output Format

After executing the program, print each value remaining on the stack, one per line, from bottom to top. If the stack is empty, print nothing. Boolean values should be printed as true and false.

Error Handling

All test inputs are guaranteed to be valid StackLang programs. You do not need to handle errors such as stack underflow, division by zero, undefined variables, or type mismatches.


Stage 1: Numbers and Arithmetic (2 marks)

Implement the following features:

  • Integer literals: Push the integer onto the stack. May be negative (e.g., -5).
  • +: Pop two values, push their sum.
  • -: Pop two values, push their difference. (10 3 -7)
  • *: Pop two values, push their product.
  • /: Pop two values, push their quotient, truncated towards zero. (10 3 /3)
  • %: Pop two values, push their remainder. (10 3 %1)

Examples

echo "3 4 +" | 6991 cargo run
7
echo "10 3 -" | 6991 cargo run
7
echo "6 7 *" | 6991 cargo run
42
echo "17 5 /" | 6991 cargo run
3
echo "17 5 %" | 6991 cargo run
2
echo "1 2 3" | 6991 cargo run
1
2
3
echo "2 3 + 4 *" | 6991 cargo run
20

Stage 2: Stack Manipulation (1 mark)

Implement the following stack operations:

  • dup: Duplicate the top value. [a][a, a]
  • drop: Discard the top value. [a][]
  • swap: Swap the top two values. [a, b][b, a]
  • over: Copy the second value to the top. [a, b][a, b, a]
  • rot: Rotate the top three values. [a, b, c][b, c, a]

Examples

echo "5 dup" | 6991 cargo run
5
5
echo "1 2 3 drop" | 6991 cargo run
1
2
echo "1 2 swap" | 6991 cargo run
2
1
echo "1 2 over" | 6991 cargo run
1
2
1
echo "1 2 3 rot" | 6991 cargo run
2
3
1
echo "3 dup *" | 6991 cargo run
9

Stage 3: Comparisons and Booleans (2 marks)

Implement boolean values and comparison operations:

  • true: Push the boolean value true.
  • false: Push the boolean value false.
  • <: Pop two integers, push true if less-than. (3 5 <true)
  • >: Pop two integers, push true if greater-than. (3 5 >false)
  • =: Pop two integers, push true if equal. (5 5 =true)
  • and: Pop two booleans, push their logical AND.
  • or: Pop two booleans, push their logical OR.
  • not: Pop one boolean, push its logical negation.

Examples

echo "3 4 <" | 6991 cargo run
true
echo "5 5 =" | 6991 cargo run
true
echo "10 3 >" | 6991 cargo run
true
echo "true false and" | 6991 cargo run
false
echo "true false or" | 6991 cargo run
true
echo "true not" | 6991 cargo run
false
echo "3 5 < 10 10 = and" | 6991 cargo run
true

Stage 4: Variables (2 marks)

Implement variable storage and retrieval:

  • set <name>: Pop the top value and store it in variable name.
  • get <name>: Push the value of variable name onto the stack.

Variable names consist of letters and underscores only (e.g., x, counter, my_var).

All variables have global scope.

Examples

echo "10 set x get x get x +" | 6991 cargo run
20
echo "5 set a 3 set b get a get b *" | 6991 cargo run
15
echo "1 set x get x 1 + set x get x" | 6991 cargo run
2
echo "true set flag get flag not" | 6991 cargo run
false

Stage 5: Conditionals (1 mark)

Implement conditional execution:

  • if <then-body> then: Pop a boolean. If true, execute then-body.
  • if <then-body> else <else-body> then: Pop a boolean. If true, execute then-body; otherwise execute else-body.

Conditionals may be nested.

Examples

echo "5 3 > if 1 then" | 6991 cargo run
1
echo "5 3 < if 1 then" | 6991 cargo run

echo "5 3 > if 100 else 200 then" | 6991 cargo run
100
echo "5 3 < if 100 else 200 then" | 6991 cargo run
200
echo "true if true if 1 then then" | 6991 cargo run
1
echo "10 set x get x 5 > if get x 2 * else get x 2 / then" | 6991 cargo run
20

Stage 6: Loops and Functions (2 marks)

Implement loops and user-defined functions:

Loops

  • loop <body> end: Execute body repeatedly until break is encountered.
  • break: Exit the innermost loop immediately.

Functions

  • fn <name> <body> end: Define a function with the given name and body. The function is not executed at definition time.
  • call <name>: Execute the function with the given name.

Functions may call other functions, including themselves (recursion).

Loops may be nested, and break only exits the innermost loop.

Examples

echo "0 loop dup 5 < if dup 1 + else break then end" | 6991 cargo run
0
1
2
3
4
5
echo "fn square dup * end 5 call square" | 6991 cargo run
25
echo "fn double 2 * end 3 call double call double" | 6991 cargo run
12
echo "fn fact dup 1 > if dup 1 - call fact * then end 5 call fact" | 6991 cargo run
120
echo "1 set i loop get i 5 > if break then get i get i 1 + set i end drop" | 6991 cargo run
1
2
3
4

Summary of Marks

Stage Features Marks
1 Numbers and arithmetic (+, -, *, /, %) 2
2 Stack manipulation (dup, drop, swap, over, rot) 1
3 Comparisons and booleans (<, >, =, and, or, not) 2
4 Variables (set, get) 2
5 Conditionals (if...then, if...else...then) 1
6 Loops and functions (loop...end, break, fn...end, call) 2
Total 10

Partial marks may be awarded for partially working implementations within each stage.


Write your answer in exam_q7/src/main.rs.

When you think your program is working, you can use autotest to run some simple automated tests:

6991 autotest

When you are finished working on your answer, submit your work with give:

cd src/
give cs6991 exam_q7 main.rs


Submission

When you are finished each exercise make sure you submit your work by running give.

You can run give multiple times. Only your last submission will be marked.

Don't submit any exercises you haven't attempted.

Do not leave it to the deadline to submit your answers. Submit each question when you finish working on it. Running autotests does not automatically submit your code.

If you are working at home, you may find it more convenient to upload your work via give's web interface.

Remember you have until 2026-01-07 17:00:00, Sydney time, to complete this exam (not including any extra time provided by ELPs).

You cannot obtain marks by e-mailing your code to tutors or lecturers.

6991 classrun -check exam_q1_1
6991 classrun -check exam_q1_2
6991 classrun -check exam_q1_3
6991 classrun -check exam_q2
...
6991 classrun -check exam_q7

- End of examination. -