T–120 minutes to launch.
Mission Control has rejected the mission configuration. Implement the MON compiler before the countdown reaches zero.
Mission Control uses MON (Mission Object Notation) to exchange
spacecraft configuration data between independent flight systems —
guidance, propulsion, life support, and ground control all read and write
the same .mon files. Before a mission can be launched, every
configuration file must be validated and converted into a deterministic
canonical form, so that cryptographic signatures computed over the
file stay consistent no matter which system produced it or in what order
its fields were written. A reordered key, a stray space, or an
inconsistently formatted integer would break the signature — and a
broken signature holds the countdown.
Your task is to implement the MON parser and canonicalizer used by
Mission Control: a program that reads a .mon file, validates it against
the specification below, and re-emits it in canonical form — or reports
exactly what's wrong with it, so the file can be fixed before it reaches
the pad.
There is no library for this. You write the parser.
A .mon file describes exactly one mission object: a flat, unordered
set of unique key = value pairs, wrapped in a mission { ... } block.
No nesting, no arrays, no comments.
mission {
commander = @alice;
emergency = no;
fuel = 4200;
name = "Apollo-X"
}
Your program must:
- Parse the source text against the grammar in §3.
- Validate it against every rule in §2 and §4.
- On success, print the object in canonical form (§4).
- On failure, print a single
Error: ...line and exit non-zero — never an unhandled exception.
- Lowercase ASCII letters only,
a–z. - Unique within the object. Duplicate keys are an error — report it at the second (repeated) occurrence, not the first.
| Example | Valid? |
|---|---|
commander |
✅ |
fuel |
✅ |
Commander |
❌ uppercase |
launch_code |
❌ underscore |
drone1 |
❌ digit |
Exactly four value types are supported.
| Type | Syntax | Examples |
|---|---|---|
| Integer | 0 or a non-zero digit followed by digits |
0, 7, 4200, 88451 |
| String | double-quoted, single line | "Apollo-X" |
| Boolean | one of two literal keywords | yes, no |
| Crew identifier | @ followed by lowercase letters |
@alice, @bob |
Integers — positive or zero, no leading zeros. A single
underscore _ may appear between two digits as a separator, purely for
human readability in the source — it carries no meaning and is stripped
when the value is canonicalized (§4).
| Example | Valid? |
|---|---|
0 |
✅ |
19 |
✅ |
4200 |
✅ |
4_200 |
✅ digit separator |
88_451_200 |
✅ digit separator |
01 |
❌ leading zero |
0042 |
❌ leading zero |
_400 |
❌ leading underscore |
400_ |
❌ trailing underscore |
4__200 |
❌ consecutive underscores |
0_400 |
❌ 0 cannot take a separator |
Tokenization rule: an integer token is the maximal contiguous run of
digits and underscores ([0-9_]+) — read the whole run before judging
it, don't stop at the first offending character. Validate that full run
against the integer production in §3; if it doesn't match, report
Error: Invalid integer "<run>" (e.g. Error: Invalid integer "400_"),
not an unrelated "unexpected character" error.
Strings — double-quoted, single line only. A newline before the closing quote is an error.
Booleans — exactly yes or no. No true/false, no other casing.
Crew identifiers — @ immediately followed by one or more lowercase
letters. No digits, no uppercase.
mission {
key = value;
key2 = value;
key3 = value
}
- The object begins with the keyword
missionfollowed by{, and ends with}. - Entries are separated by
;. - There is no trailing semicolon after the last entry.
- Whitespace (spaces, tabs, newlines) is insignificant and may appear anywhere between tokens.
- Comments are not supported.
- The empty mission
mission {}is valid.
mission = "mission" "{" [ member { ";" member } ] "}" ;
member = key "=" value ;
key = lowercase { lowercase } ;
value = integer | string | boolean | crewid ;
integer = "0" | nonzero-digit { integer-tail } ;
integer-tail = digit | "_" digit ;
string = '"' { any-char-except-quote-or-newline } '"' ;
boolean = "yes" | "no" ;
crewid = "@" lowercase { lowercase } ;
lowercase = "a" | "b" | ... | "z" ;
digit = "0" | "1" | ... | "9" ;
nonzero-digit = "1" | "2" | ... | "9" ;Given any valid mission object, print it back out as follows:
- Keys sorted alphabetically.
- Integer digit separators (
_) are removed — canonical integers are a plain digit sequence, e.g.4_200canonicalizes to4200. - Exactly one space on each side of
=. - One entry per line, indented 4 spaces.
- Semicolons between entries, omitted after the final entry — the canonical form keeps the same separator style as the source.
mission {and}each on their own line.- The empty mission canonicalizes to
mission {}on a single line.
Input:
mission {
fuel = 4200;
name = "Apollo-X";
commander = @alice;
emergency = no
}
Canonical output:
mission {
commander = @alice;
emergency = no;
fuel = 4200;
name = "Apollo-X"
}
Digit separators are stripped, not preserved:
Input:
mission {
launchcode = 88_451_200
}
Canonical output:
mission {
launchcode = 88451200
}
Malformed input must produce a clear Error: <description> line — never a
stack trace. At minimum, detect and report:
- Unexpected / unrecognized character
- Missing
=in a member - Invalid integer (leading zeros; misplaced, trailing, leading, or consecutive digit-separator underscores)
- Invalid key (uppercase, digits, underscores)
- Duplicate key
- Trailing semicolon before
} - Missing
{or} - Unterminated string
Examples:
Error: Duplicate key "commander"
Error: Invalid integer "00451"
The remaining cases just need to follow the Error: <description> shape
and clearly identify the problem. Sample failures, each with its expected
output:
| Input | Expected error |
|---|---|
mission { name = "Sam" # } |
Error: Unexpected character '#' ... |
mission { name "Sam" } |
Error: Expected '=' after key "name" ... |
mission { name = "Sam } |
Error: Unterminated string ... |
mission { name = "Sam"; } |
Error: Trailing semicolon before '}' is not allowed |
mission { name = "Sam" (no closing brace) |
Error: Expected '}' to close mission object ... |
- Python 3, standard library only.
- Parser generators are forbidden (no ANTLR, Lark, PLY, etc.).
- Parsing libraries are forbidden (no
json,yaml,toml,pyparsing, etc. — even though MON is simple enough thatjsoncouldn't parse it anyway). - The implementation must be written by hand.
Beyond that, any architecture is acceptable as long as it satisfies the specification. Tokenizer + recursive-descent parser, a single-pass state machine, whatever you're most comfortable with.
Your entry point is a single function:
def canonicalize(source: str) -> str:
...which takes raw .mon source text and returns its canonical form, raising
MissionSyntaxError (with a message following §5) on invalid input.
| File | Purpose |
|---|---|
README.md |
This document |
mon_simple.py |
Starter — implement canonicalize here |
input1.mon, input2.mon |
Valid sample missions |
expected1.mon, expected2.mon |
Exact expected canonical output |
invalid1.mon |
Invalid sample — duplicate key |
invalid2.mon |
Invalid sample — leading-zero integer |
mission {
fuel = 4200;
name = "Apollo-X";
commander = @alice;
emergency = no
}
mission {
commander = @alice;
emergency = no;
fuel = 4200;
name = "Apollo-X"
}
mission {
pilot=@bob;
destination = "Europa Station";
droneid = 7;
emergency=yes;
launchcode = 88451
}
mission {
destination = "Europa Station";
droneid = 7;
emergency = yes;
launchcode = 88451;
pilot = @bob
}
mission {
commander = @alice;
pilot = @bob;
commander = @carol
}
Expected: Error: Duplicate key "commander"
mission {
launchcode = 00451;
pilot = @bob
}
Expected: Error: Invalid integer "00451"
Submissions are scored against the sample files above and a held-out suite of additional hidden test cases covering the same rules — further key/value edge cases, additional malformed inputs, and boundary conditions (e.g. very long integers, empty strings, whitespace placement). Passing the samples in this repository is necessary but not sufficient for full marks.
| Category | Points |
|---|---|
| Correct parsing of all four value types | 20 |
| Correct canonical output (samples + hidden tests) | 25 |
| Correct error detection and messages (samples + hidden tests) | 25 |
Edge cases (empty mission, 0, single-entry object, etc.) |
10 |
| First/Second/Third Submission | 15/10/5 |
| Code quality: clean, readable, sensibly organized | 5 |
| Total | 100 |
Extend your implementation to support nested mission blocks as a
value type — i.e. a value may itself be a mission { ... } block — without
changing the public interface (canonicalize(source: str) -> str keeps
its signature, and flat missions like the samples above must still work
exactly as before).
This is not required for full marks and is scored separately from the core rubric.