Introduction
Sunny Snacks needs a receipt that keeps every purchase, including a customer returning for the same snack a second time. You will first run a one-item sample, then complete a C program that stores separate purchase records and prints every line, its unit price, its subtotal and the complete total.
You need C variables, conditions, loops, arrays, functions and basic terminal input and paths. This lab introduces the required struct and text-output details. The browser environment already includes a compiler and the Workspace terminal. The amounts are synthetic integer cents, not a payment or accounting system. Reading and practice time is an author estimate; beginner timing has not been measured.
Read a One-Item Receipt
In this step, you will run a complete small sample and change a one-item purchase into a two-item purchase.
Open the Workspace Tab. Its terminal starts in /home/labex/project/receipts. cd changes the directory; pwd prints it and ls lists the files. These commands also give you a recovery route after a wrong path or file name.
cd /home/labex/project/receipts
pwd
ls
sample.c is a complete one-item example. till.c is the unfinished multi-item program for the next step. The supplied catalog, parser and input driver are prepared files; do not change them. cat displays a text file without editing it:
cat sample.c
The sample's quantity starts at 1. unit_cents is 125: one whole unit and 25 cents. The multiplication calculates a subtotal in cents. Keep that unit throughout the calculation. Integer division by 100 gives the whole part; % 100 gives the remainder. In printf, %d prints an integer, %s prints a terminated C string, %02d pads the fractional part to two digits, and \n ends the line. Thus 250 cents becomes 2.50 without floating-point arithmetic.
Compile the program. gcc is the C compiler, -std=c11 selects C11, -Wall -Wextra enable warnings, and -o sample names the executable. ./sample runs that executable from this directory.
gcc -std=c11 -Wall -Wextra sample.c -o sample
./sample
The complete normal result is:
SUNNY SNACKS
ID | ITEM | QTY | UNIT | SUBTOTAL
101 | Oat bar | 1 | 1.25 | 1.25
TOTAL | 1.25
ID means product identifier: 101 refers to this catalog entry. UNIT is the price of one item; SUBTOTAL is this purchase's quantity times its unit price. The pipe characters separate readable columns. The final TOTAL adds all purchase subtotals; here there is only one.
Change the quantity to 2. The following sed command substitutes the exact old text with new text; -i saves the edit. Single quotes keep the substitution together as one shell argument.
sed -i 's/int quantity = 1;/int quantity = 2;/' sample.c
Recompile after editing: source changes do not update an old executable automatically. The shell operator > redirects standard output into a file. It opens or truncates that file before C starts, so use the new output name below.
gcc -std=c11 -Wall -Wextra sample.c -o sample
./sample > sample-receipt.txt
cat sample-receipt.txt
The quantity is now 2 and both subtotal and total are 2.50; unit price remains 1.25. Check this by hand: 125 × 2 = 250 cents. Keep sample.c, executable sample and sample-receipt.txt.
If a file is missing, return with the taught cd and use ls to check its spelling. If compilation reports an error, fix the line it names and compile again before running; an old executable may still exist after a failed compile.
Keep and Print Every Purchase
In this step, you will keep separate purchase records and generate a complete three-line receipt.
Start in Workspace, in /home/labex/project/receipts. Read the catalog and unfinished program:
cd /home/labex/project/receipts
cat catalog.h
cat till.c
The header file catalog.h supplies the shop title, maximum six purchase lines, maximum quantity 20, and four catalog rows. #include "catalog.h" includes those definitions during compilation. #define gives a name to a constant or initializer; CATALOG_ROWS supplies the rows used to initialize the product array. A backslash (\) at the end of a catalog definition line continues the same #define onto the next physical line. Keep catalog.h, parse.h, driver.h and read-line.h unchanged in this lab.
A struct groups related fields into one record. Product groups an integer id, a 32-character name array and an integer cents price. typedef lets the program use Product as that record type's short name. A C string ends with the zero-valued character \0, so a name array needs room for the terminator as well as its visible characters. The provided names fit; this task does not parse arbitrary names.
Purchase is a second record type. It keeps the product's array position and that purchase's quantity. In basket[line_count].quantity, the brackets select one purchase and the dot selects its quantity field. Array positions start at zero. The static global array and line_count begin zero-initialized. sizeof products / sizeof products[0] computes the number of complete product records in the array.
The supplied find_product loop matches a product ID, returning its array position or -1 when absent. The two products named Water have different IDs and prices: 103 is 75 cents; 104 is a free item. Names alone cannot identify a product. The catalog has four products, with IDs from 1 through 999 and prices from 0 through 9999 cents. The same program should also work when recompiled with another supplied four-row catalog within those limits.
Complete approve_purchase by checking the ID, quantity and remaining capacity before writing an array entry. Only then store the product position and quantity at basket[line_count] and increase the count. Quantity is 1 through 20; zero price is valid. Do not merge repeated product IDs: a later purchase is another line. Six lines at the largest allowed price and quantity total at most 6 × 20 × 9999 = 1,199,880 cents, within a 32-bit signed integer.
puts prints a terminated string and adds a newline. printf uses format placeholders such as %s for a NUL-terminated string and %d for an integer. print_receipt loops over every approved line. Product product = ... copies that catalog record into a local variable. Calculate each integer subtotal, add it to the total, and print the complete ordered rows between the title/header and one final TOTAL. Use the given five pipe-separated fields; spaces around each field and blank lines may vary, but extra records or missing fields do not form a complete receipt.
Replace till.c with the complete source below. cat > till.c replaces that file; <<'EOF' begins a here-document. The quoted delimiter copies C text literally until the line containing only EOF. Copy the entire block, including that last line.
cat > till.c <<'EOF'
#include <stdio.h>
#include "catalog.h"
#include "parse.h"
typedef struct {
int id;
char name[32];
int cents;
} Product;
typedef struct {
int product_index;
int quantity;
} Purchase;
static const Product products[] = CATALOG_ROWS;
static const int product_count = sizeof products / sizeof products[0];
static Purchase basket[MAX_LINES];
static int line_count;
static int find_product(int id) {
for (int i = 0; i < product_count; i++)
if (products[i].id == id) return i;
return -1;
}
static int approve_purchase(int id, int quantity) {
int index = find_product(id);
if (index < 0 || quantity < 1 || quantity > MAX_QUANTITY || line_count >= MAX_LINES)
return 0;
basket[line_count].product_index = index;
basket[line_count].quantity = quantity;
line_count++;
return 1;
}
static void print_receipt(void) {
int total = 0;
puts(SHOP_TITLE);
puts("ID | ITEM | QTY | UNIT | SUBTOTAL");
for (int i = 0; i < line_count; i++) {
Product product = products[basket[i].product_index];
int quantity = basket[i].quantity;
int subtotal = product.cents * quantity;
total += subtotal;
printf("%d | %s | %d | %d.%02d | %d.%02d\n",
product.id, product.name, quantity,
product.cents / 100, product.cents % 100,
subtotal / 100, subtotal % 100);
}
printf("TOTAL | %d.%02d\n", total / 100, total % 100);
}
#include "driver.h"
EOF
parse.h and driver.h provide the already working input loop. The driver asks for one product ID, then a quantity, and calls your approval and printing functions. Their existing names/signatures are integration points; keep them as shown. Internal record type and local variable names are not the scoring goal. You will learn the parsing boundary in the next lab.
Compile the completed program:
gcc -std=c11 -Wall -Wextra till.c -o till
The driver sends prompts and messages to standard error, which stays visible in the terminal. Only the completed receipt goes to standard output. Run it with a new output file:
./till > receipt.txt
The program is now waiting for input. Type each value below at its prompt, pressing Enter after each. Do not type these values as shell commands. done is the exact lowercase checkout command and works only at the Product ID prompt.
| Product ID input | Quantity input | Result |
|---|---|---|
101 |
2 |
Added line 1 |
102 |
1 |
Added line 2 |
101 |
1 |
Added line 3, keeping the earlier 101 purchase |
done |
No quantity prompt | Complete receipt and return to the shell |
You can still see prompts and Added line messages because the prepared driver writes those to standard error, a separate output stream. Only the completed receipt goes to standard output, which > sends to receipt.txt. Once the shell prompt returns, read the actual saved receipt:
cat receipt.txt
Expected contents:
SUNNY SNACKS
ID | ITEM | QTY | UNIT | SUBTOTAL
101 | Oat bar | 2 | 1.25 | 2.50
102 | Fruit cup | 1 | 2.30 | 2.30
101 | Oat bar | 1 | 1.25 | 1.25
TOTAL | 6.05
The two Oat bar purchases stay separate. Add their subtotals with Fruit cup: 250 + 230 + 125 = 605 cents. A plausible total without these complete rows would lose the customer's transaction details.
Numeric inputs use decimal digits, an optional sign, and optional leading/trailing ASCII spaces or Tabs; leading zeros are allowed. IDs must be in the catalog, quantities 1..20, and each line at most 63 bytes before Enter. A rejected ID/quantity discards the pending candidate and returns to Product ID without changing approved purchases. The provided reader drains an overlong line instead of splitting it into later inputs. At most six purchases can be approved. An empty basket followed by done is a valid empty receipt with total 0.00.
To cancel instead of checking out, press Ctrl+D at an empty input prompt. This end-of-file signal, or EOF during an unfinished input line, cancels with status 2 and no completed receipt. It does not export or preserve an unfinished transaction. Redirection has already opened its output file; use a different file name if you deliberately test cancellation. A done entered as a quantity is invalid quantity text, not checkout.
If your receipt has only the last purchase, check that you store at basket[line_count] and increase the count after each approved line. Save, recompile, and run a new transaction with the same required purchases. Each execution begins with an empty basket; the program does not reload old receipt text. Keep till.c, executable till, supplied files and complete receipt.txt. The course check also runs unchanged source copies with runtime bounds detection in a disposable directory. It can detect errors exercised by those inputs; it does not prove complete memory safety or repair your files.
Summary
You turned a one-item sample into a complete multi-line receipt. You used structs, an array of separate purchases, stable catalog IDs and integer-cent arithmetic, then saved and read the real text result. The next lab focuses on the input boundary before a purchase can enter that array.


