What you are building
This module covered a lot of ground: memory and the CPU, files and paths, binary and text, the operating system, and processes. In this assignment you tie it together by writing a single program, the Computer Toolkit. It reads commands from standard input, one per line, and prints one line of answer for each. Each command exercises one lesson of the module.
It is a filter: it reads lines from standard input and writes results to standard output, with no windows and no prompts. That is the shape of the programs you will run in the judge later in the course, and it is the same shape that lesson 5 described with standard input, standard output and pipes. You may write it in Python (the examples below use it) or in JavaScript with Node. Choose Python unless you have a reason not to.
You will need about an hour. Work through the spec, write the commands in the order given, and test as you go. Do not open the solution until you have a version that runs.
The specification
The program reads standard input until it ends. For each line:
- Ignore the line if it is empty or only spaces.
- Split the line into words on spaces. The first word is the command, and the rest are its arguments.
- Print exactly one line of output.
- If the command is not in the table below, print
error: unknown command NAMEwhere NAME is what was typed. - If the command is known but its arguments are missing, extra or invalid, print
error: bad input. - Never crash. One bad line must not stop the program. The next line is handled as normal.
The commands
| Command | Arguments | Output | Lesson |
|---|---|---|---|
bin | a whole number N, 0 or more | N in binary, with no prefix and no padding | 3 |
dec | a string of 0s and 1s | the decimal value | 3 |
hex | a whole number N, 0 or more | N in lowercase hex, no prefix | 3 |
bytes | the rest of the line, as text | the number of UTF-8 bytes, then each byte as two lowercase hex digits | 3 |
size | a number of bytes | a human-readable size using binary units | 1, 2 |
perm | three octal digits | the nine-character permission string | 2, 4 |
path | one path | the name, stem, suffix and parent | 2 |
rr | a slice length, then jobs written as NAME:UNITS | the finishing time of every job, in finishing order | 5 |
Here are the exact formats.
bin N. bin 13 prints 1101. bin 0 prints 0. A negative number, a decimal point or text is error: bad input. (A negative number written -4 is not a run of digits, so a simple digit check rejects it.)
dec BITS. dec 11001010 prints 202. Any character other than 0 or 1 is error: bad input, so dec 1021 is an error.
hex N. hex 255 prints ff.
bytes TEXT. The text is everything after the word bytes, joined back with single spaces. The output is the byte count, the words bytes: and then the bytes in hex separated by single spaces. bytes Hi! prints 3 bytes: 48 69 21. bytes café prints 5 bytes: 63 61 66 c3 a9, because é takes two bytes in UTF-8. If there is no text at all, it is error: bad input.
size N. Use the units B, KiB, MiB, GiB and TiB, each 1024 times the previous one. Pick the largest unit where the value is at least 1. Below 1024 bytes, print the plain integer and the letter B, such as 500 B. Otherwise print the value to one decimal place, a space and the unit: size 1536 prints 1.5 KiB, and size 5368709120 prints 5.0 GiB.
perm DDD. Each digit is 0 to 7. Turn each one into r, w, x or - using the 4, 2 and 1 bits. perm 644 prints rw-r--r--. perm 755 prints rwxr-xr-x. A digit of 8 or 9, or the wrong number of digits, is error: bad input.
path P. Print name=... stem=... suffix=... parent=..., where the stem is the name without its last extension, the suffix is that extension including the dot (empty if none), and the parent is everything before the last slash, or . if there is no slash. Use forward slashes. path /home/anisha/course/hello.py prints name=hello.py stem=hello suffix=.py parent=/home/anisha/course. For path notes.tar.gz, only the last dot counts, so it prints name=notes.tar.gz stem=notes.tar suffix=.gz parent=..
rr SLICE JOB JOB .... This is the round-robin scheduler from lesson 5. SLICE is a whole number above 0. Each job is NAME:UNITS with UNITS a whole number above 0. Jobs start in the order given. Each turn a job runs for the smaller of SLICE and its remaining units, the clock moves forward by that much, and an unfinished job goes to the back of the queue. Print NAME@TIME for each job as it finishes, separated by single spaces. rr 2 A:5 B:3 C:4 prints B@9 C@11 A@12.
A full example
Input:
bin 13
bin 0
dec 11001010
dec 1021
hex 255
bytes Hi!
bytes café
size 500
size 1536
size 5368709120
perm 644
perm 755
perm 689
path /home/anisha/course/hello.py
path notes.tar.gz
rr 2 A:5 B:3 C:4
rr 3 A:7 B:2 C:5
fly 3
hex -4Expected output:
1101
0
202
error: bad input
ff
3 bytes: 48 69 21
5 bytes: 63 61 66 c3 a9
500 B
1.5 KiB
5.0 GiB
rw-r--r--
rwxr-xr-x
error: bad input
name=hello.py stem=hello suffix=.py parent=/home/anisha/course
name=notes.tar.gz stem=notes.tar suffix=.gz parent=.
B@9 C@11 A@12
B@5 C@13 A@14
error: unknown command fly
error: bad inputThe blank line in the input produces no output. There are 19 non-blank input lines and 19 lines of output.
Requirements checklist
Copy this into your notes and tick the boxes as you go.
- The program reads all of standard input, line by line, and stops at the end.
- Empty lines are skipped without output.
-
binconverts whole numbers, including 0, and rejects negatives and text. -
decconverts binary strings and rejects any other character. -
hexprints lowercase hex without a prefix. -
bytescounts UTF-8 bytes, not characters, and shows each byte in two hex digits. -
sizepicks the right unit and prints one decimal for KiB and above. -
permturns three octal digits into a nine-character string. -
pathsplits a path into name, stem, suffix and parent, with.for no parent. -
rrsimulates round-robin scheduling and prints the finishing times in finishing order. - Unknown commands print
error: unknown command NAME. - Invalid arguments print
error: bad input, and no input can crash the program. - Your output matches the full example above exactly.
- Each command is its own function, and no function is longer than about 20 lines.
How to run it
You can run the program in the box that follows the reference solution, by typing commands into the Input box. On your own machine, save the file as toolkit.py and run it in two ways. Typing at a prompt works (press Ctrl+D on macOS and Linux, or Ctrl+Z then Enter on Windows, to end the input):
python3 toolkit.pyOr feed it a file of commands, which is how you will test it. Put the sample input in sample.txt, then run:
python3 toolkit.py < sample.txtTo compare against the expected output automatically, save the expected lines in expected.txt and use the diff tool, which prints nothing when two files are the same:
python3 toolkit.py < sample.txt > actual.txt
diff actual.txt expected.txtGrading rubric
Use this to mark yourself honestly. Each level includes everything in the one below it.
A 60 percent solution runs and handles the number commands. bin, dec, hex and perm give the right output for valid input. It may crash on bad input or may not recognise unknown commands. The code can be one long block. It shows that you understand place values and the permission bits.
An 80 percent solution adds bytes, size and path, and handles errors. Unknown commands and invalid arguments print the specified messages, and the program never crashes on the sample input. It passes the full example. The code is split into functions, one per command. It shows that you understand text encoding, memory units and paths.
A 100 percent solution also includes rr, and passes every edge case you can think of: no arguments, extra arguments, a huge number such as 99999999999999999999, a slice of 0, a job with 0 units, a job written without a colon, text with several spaces, and a path with no slash. The code uses a table (a dictionary) to look up commands instead of a chain of ifs. Every function has a clear name. You tested it with your own input file and compared it with diff. It shows that you understand scheduling and can write a program that is tidy and robust.
Hints
Try each step before opening its hint. Hints are ordered from small nudges to near-answers.
Hint 1: the shape of the program
Read lines with for line in sys.stdin:. Strip the line, skip it if empty, split it into words with line.split(). The first word is the command. Everything else is the argument list. Start by printing the command and arguments to see that parsing works, and write each command afterwards.
Hint 2: bin, hex and the digit check
str.isdigit() is true when a string is made only of the characters 0 to 9. It is false for -4 and 3.5 and for the empty string, which is exactly what you want. Python's format(n, "b") gives binary and format(n, "x") gives lowercase hex. Or write your own divide-by-2 loop from lesson 3, which is good practice.
Hint 3: dec and validation
A string is valid when it is not empty and every character is 0 or 1. int(text, 2) then does the conversion. Check first, convert second. Do not rely on the conversion failing, because int accepts things like " 11" and "1_0".
Hint 4: bytes
" ".join(args) rebuilds the text. .encode("utf-8") turns it into bytes. len of that is the byte count. For each byte b, format(b, "02x") gives two lowercase hex digits. Join them with spaces.
Hint 5: size
Keep a list of unit names. Start with the value as a float and an index of 0. While the value is at least 1024 and there is a bigger unit left, divide by 1024 and add 1 to the index. If the index is still 0, print the original integer with B. Otherwise print with f"{value:.1f}".
Hint 6: perm
Loop over the three characters. For each, make an integer d. The read letter is shown when d & 4 is not zero, write when d & 2 is not zero, execute when d & 1 is not zero. Build the answer by adding three characters per digit.
Hint 7: path
The module posixpath has basename, dirname and splitext. splitext("notes.tar.gz") returns ("notes.tar", ".gz"). dirname of a bare name returns an empty string, so replace that with ..
Hint 8: rr
Use a queue: collections.deque, with popleft to take the front and append to add to the back. Keep a tick counter. Take a job, work out work = min(slice_len, left), add it to tick, subtract it from left. If left is above 0, put the job back. Otherwise record name@tick. For each argument, item.split(":") gives the name and the units. Wrap the parsing so that a missing colon raises ValueError.
Hint 9: never crash
Make every command function raise ValueError when its input is wrong. In the main loop, call the command inside try, and catch ValueError to print error: bad input. A line with the wrong number of words, a missing colon and a non-number all end in ValueError if you write the checks carefully. int("abc") already raises it. Be careful with int(args[0]) when args might be empty, which raises IndexError. Check the length of the arguments first.
Reference solution
Try to finish before reading this. When you do read it, do not copy it. Compare it with your own code and ask where the two differ and why.
Reference solution (Python)
To run it here, type the commands from the full example into the Input box, one per line, and press Run. The program prints one line per command.
How it is organised. Each command has its own function, which either returns the line to print or raises ValueError. The COMMANDS dictionary maps each command name to its function, so adding a command later takes one function and one dictionary line. main does only the loop: read, split, look up, call, catch.
Why the checks look the way they do. parse_int insists on exactly one argument made of digits, so bin, hex and size share the same validation. cmd_dec and cmd_perm check characters against the allowed set before converting. cmd_rr converts and then checks that every number is positive. Any failed check raises ValueError, and main turns it into the standard message.
On size. The loop divides by 1024 while the value is large enough and a larger unit exists, so a petabyte-sized number stays in TiB rather than running off the end of the list. The B case prints the integer, so 500 does not become 500.0.
On path. The module posixpath always uses / as the separator, whichever system runs the program, which keeps the output the same everywhere.
On rr. The loop is the scheduler from lesson 5. The finishing time is the value of tick after the final slice of a job.
Test your own edge cases
A strong solution is judged by what it does with awkward input, so write a second input file that tries to break your program. Here are some ideas, with what a correct program does:
| Input | Correct output | Why |
|---|---|---|
bin | error: bad input | no argument |
bin 5 6 | error: bad input | extra argument |
bin 007 | 111 | leading zeros are fine, the value is 7 |
bin 99999999999999999999 | the binary form, 67 digits | Python integers do not overflow |
dec | error: bad input | nothing to convert |
dec 0 | 0 | a valid single bit |
hex 0 | 0 | zero has one digit |
size 0 | 0 B | smaller than 1024 |
size 1024 | 1.0 KiB | exactly one unit |
size 1023 | 1023 B | just under |
perm 64 | error: bad input | two digits |
perm 000 | --------- | no rights |
path / | name= stem= suffix= parent=/ | nothing after the slash |
path .bashrc | name=.bashrc stem=.bashrc suffix= parent=. | a leading dot is part of the name, not a suffix |
rr 0 A:2 | error: bad input | a slice of 0 would never end |
rr 5 A:2 | A@2 | one job, shorter than the slice |
rr 2 A:x | error: bad input | units are not a number |
rr 2 A | error: bad input | no colon |
BIN 5 | error: unknown command BIN | commands are case-sensitive |
The bin 99999999999999999999 row is a good test of lesson 1 and lesson 3 together. Languages with fixed-size integers would overflow on it, as you saw with bytes and with JavaScript numbers beyond 9,007,199,254,740,991. Python grows the number as needed, so the answer stays right. In JavaScript you would need BigInt to match it.
Stretch extensions
If you finish early, extend the toolkit. Each extension touches an idea from the module.
filecommand. Takes a hex string of the first few bytes and reports the format using the signatures from lesson 2:file 89504e47printsPNG image.ipcommand. Converts192.168.1.10to the 32-bit number and to dotted binary, as in lesson 3.asciicommand. Prints the code point and 8-bit binary for each character of a word, like theHi!table.negcommand. Prints the 8-bit two's complement pattern of a number from -128 to 127.- A
--verboseflag. When the program is started with--verboseas an argument (readsys.argv), print a trace line for every scheduling step inrr. This connects with the arguments you learnt about in lesson 5. - Exit code. Make the program exit with code 1 if any line produced an error, and 0 otherwise. Check it in the shell with
echo $?. This connects with the exit codes in lesson 5.
What you now know
This assignment used every idea of the module in one small program. As a closing summary of the whole module:
- A computer is a CPU that runs fetch, decode and execute, working on data in RAM, with storage that keeps data when the power is off.
- A file is a named run of bytes, found by a path. Extensions are hints, the bytes are the truth, and permissions control access.
- All data is numbers: whole numbers in binary, text through ASCII and Unicode, images as grids of pixels, decimals as approximations.
- The operating system manages hardware, memory, files and users, and programs reach it through system calls.
- A process is a running program with its own memory. The scheduler shares the CPU by time slicing, and exit codes, signals and standard streams connect processes to the world.
- You can read the usual errors,
No such file or directory,Permission denied,command not found,Segmentation fault, as short, precise reports from the machine.