Theory test — Chapter 1¶
56 questions; you pass at 80 %. This page shows the questions only: take the test in the terminal, where every answer is graded and explained.
./course quiz 1 # interactive
./course quiz template 1 -o answers/ch01.yaml # or fill in a file ...
./course quiz grade 1 # ... and grade it
nested-comments-not-regular · multi · 1 pt · 01-regular-languages-and-thompsonYou want to prove with the pumping lemma (pumping length \(p\)) that Pebble's nested block comments, \(C = \{\, (\texttt{/*})^{k}\,x\,(\texttt{*/})^{k} \mid k \ge 1 \,\}\) restricted to \(x = \varepsilon\), are not regular. Which statements are correct steps of the proof?
- Pick \(w = (\texttt{/*})^{p}(\texttt{*/})^{p}\), which is in \(C\) and has length \(\ge p\).
- Pick the decomposition \(w = xyz\) so that \(y\) is exactly one
/*. - For every decomposition with \(\lvert xy\rvert \le p\), \(y \ne \varepsilon\), the part \(y\) lies within the openers.
- Pumping \(y\) changes the openers but not the \(p\) closers, so \(xy^{2}z \notin C\).
- It suffices to show that one particular DFA with a depth counter needs infinitely many states.
closure-complement · single · 1 pt · 01-regular-languages-and-thompsonA lexer generator builds \(\overline{L(A)}\) by swapping final and non-final states of an automaton \(A\). For which \(A\) is this construction correct?
- Any ε-NFA
- Any DFA, even with missing transitions
- A complete DFA (a transition for every state and symbol)
- Only a minimal DFA
llvm-regex-state-sets · mapping · 1 pt · 01-regular-languages-and-thompsonIn llvm/lib/Support/regexec.c (LLVM 23.1.2), llvm_regexec chooses between two matchers compiled from the same regengine.inc.
small: the matcher used when the program's number of states is at most CHAR_BIT * sizeof(long).
large: the other matcher.
small-set: the C type that represents a set of NFA states in the small matcher.
large-set: that type in the large matcher.
small, large, small-set, large-setthompson-state-count · mapping · 1 pt · 01-regular-languages-and-thompsonFor \(r = a(b \mid c)^{*}d\):
thompson: the number of states of Thompson's construction as in Algorithm 1.1.6 (concatenation merges the accept state of the left fragment with the start state of the right one, as in the Dragon book).
position: the number of states of the position (Glushkov) automaton.
thompson, positionthompson-closure · mapping · 1 pt · 01-regular-languages-and-thompsonThompson NFA of \((a \mid b)^{*}abb\) (Dragon numbering): 0: ε→1, ε→7; 1: ε→2, ε→4; 2: a→3; 3: ε→6; 4: b→5; 5: ε→6; 6: ε→1, ε→7; 7: a→8; 8: b→9; 9: b→10; 10 accepting.
Give the ε-closures as sets: E3 = \(E(\{3\})\), E6 = \(E(\{6\})\), E8 = \(E(\{8\})\).
E3, E6, E8glushkov-follow · mapping · 1 pt · 01-regular-languages-and-thompsonLinearize \((a \mid b)^{*}ab\) as \((a_1 \mid b_2)^{*}a_3 b_4\). Give Follow for each position (as sets of position numbers).
1, 2, 3, 4backtracking-blowup · number · 1 pt · 02-nfa-simulation-and-subset-constructionA backtracking matcher runs \((a \mid a)^{*}b\) against \(a^{10}\) (no b). The star is greedy, alternatives are tried left to right, and every choice is explored before the match fails. Each time the matcher leaves the star it tries to match the final b once. How many attempts to match b does it make in total?
engine-choice · mapping · 1 pt · 02-nfa-simulation-and-subset-constructionPick the best engine for each job: backtracking, pike-vm, dfa (ahead-of-time subset construction) or lazy-dfa.
grep: search large files for a user-supplied pattern without backreferences, no submatches.
backrefs: a pattern with a backreference (a+)b\1.
captures: extract submatch positions with a guaranteed linear-time bound.
lexer: a lexer specification fixed at build time.
grep, backrefs, captures, lexersubset-states · mapping · 1 pt · 02-nfa-simulation-and-subset-constructionThompson NFA of \((a|b)^{*}a(a|b)\) has states 0–13 (Dragon numbering; the start set is \(E(\{0\}) = \{0,1,2,4,7\}\)). Run the subset construction over \(\{a, b\}\) with a FIFO worklist and name DFA states A, B, C, … in order of creation.
states: the number of DFA states (no dead state is needed here).
accepting: the accepting DFA states.
A-a, A-b: the targets of A on a and on b.
states, accepting, A-a, A-bsubset-blowup · number · 1 pt · 02-nfa-simulation-and-subset-constructionHow many states does the minimal complete DFA of \((a|b)^{*}a(a|b)(a|b)(a|b)\) have over the alphabet \(\{a, b\}\)?
lazy-dfa-states · number · 1 pt · 02-nfa-simulation-and-subset-constructionA lazy DFA runs \((a|b)^{*}a(a|b)(a|b)(a|b)\) (Thompson NFA) on the input abaabb, starting with an empty cache. How many distinct DFA states does it create, including the start state?
derivative-compute · single · 1 pt · 03-derivativesWhich expression is the Brzozowski derivative \(\partial_a\big((ab \mid a)^{*}b\big)\), simplified by similarity?
- \(b \mid (ab \mid a)^{*}b\)
- \((b \mid \varepsilon)(ab \mid a)^{*}b\)
- \((b \mid \varepsilon)b\)
- \(b(ab \mid a)^{*}b\)
- \(\emptyset\)
derivative-nullable · mapping · 1 pt · 03-derivativesFor \(r = (ab \mid a)^{*}b\) and each word \(w\), is \(\partial_w r\) nullable (yes/no)? That is, is \(w \in L(r)\)?
a, aa, aab, ab, ababantimirov-bound · number · 1 pt · 03-derivativesThe partial-derivative (Antimirov) automaton of \((a|b)^{*}abb\) has at most \(m + 1 = 6\) states (\(m\) = symbol occurrences). How many states does it actually have?
ort-lexer-tag · single · 1 pt · 03-derivativesIn an Owens–Reppy–Turon derivative lexer with rules (1) if and (2) [a-z]+, the DFA state reached by reading if is the vector \((\varepsilon,\ [a\text{-}z]^{*})\). Which rule does this state accept?
- Rule 1 (
if): the first nullable component wins - Rule 2: the longest rule wins
- Both: the lexer returns two tokens
- Neither: a state accepts only if exactly one component is nullable
derivative-classes · number · 1 pt · 03-derivativesHow many derivative classes does \(r = [a\text{-}c]x \mid [b\text{-}d]y\) induce on the 256 byte values (Definition 1.3.6)? Symbols in one class must give the same derivative \(\partial_c r\).
moore-rounds · mapping · 1 pt · 04-dfa-minimizationComplete DFA over {a, b}, start A, accepting E:
| a | b | |
|---|---|---|
| A | B | C |
| B | D | E |
| C | B | C |
| D | B | C |
| E | Z | Z |
| Z | Z | Z |
Run Moore's algorithm from {F, Q∖F}.
rounds: the number of rounds that change the partition (not counting the final confirming round).
states: the number of states of the minimal DFA.
rounds, statesmyhill-nerode-classes · number · 1 pt · 04-dfa-minimizationHow many Nerode classes (states of the minimal complete DFA over {a, b}) does \(L = (b^{*}ab^{*}ab^{*}ab^{*})^{*}\) have?
hopcroft-smaller-half · single · 1 pt · 04-dfa-minimizationIn Hopcroft's algorithm, a block \(B\) that is on the worklist as a splitter is itself split into \(B_1\) and \(B_2\). What does the algorithm put on the worklist?
- Both \(B_1\) and \(B_2\) (it replaces \(B\)); if \(B\) had not been on the worklist, only the smaller of the two
- Always only the smaller half
- Always both halves
- Nothing: splitters are fixed at the start
brzozowski-reachable · single · 1 pt · 04-dfa-minimizationWhy must the automaton given to each \(\det(\mathrm{rev}(\cdot))\) step of Brzozowski's minimization have all states reachable, and how do you reverse a DFA with several final states?
- Unreachable states make the subset construction loop; add a fresh start state with ε-edges to the old finals
- Reachability is irrelevant; reversal needs a unique final state, so first merge the finals
- Reachability of the input makes the result's states pairwise distinguishable; start the subset construction from the set of old final states
- Reachability makes the result complete; reversing is only possible with one final state
llvm-dfa-emitter · single · 1 pt · 04-dfa-minimizationllvm/utils/TableGen/DFAEmitter.cpp (LLVM 23.1.2) builds the automata of TableGen's GenAutomaton backend (VLIW packetizers). How does DfaEmitter::constructDfa deduplicate DFA states?
- It runs Hopcroft's algorithm after determinization
- It interns each sorted set of NFA states in a UniqueVector while determinizing, without minimizing afterwards
- It uses Brzozowski's double reversal
- It never deduplicates: every transition creates a new state
llvm-clang-less-case · multi · 1 pt · 05-lexer-implementation-stylesIn clang/lib/Lex/Lexer.cpp (LLVM 23.1.2), Lexer::LexTokenInternal, case '<', compiling C++20 with digraphs enabled (not CUDA, not inside #include <…>). Which token kinds can this case produce?
- less
- lessequal
- lessless
- lesslessequal
- spaceship
- l_square
- l_brace
- lesslessless
- greater
style-choice · mapping · 1 pt · 05-lexer-implementation-stylesChoose an implementation style for each lexer: switch (hand-written loop), table (flex-style), direct (re2c direct code), combinator (regex alternation), simd (bulk classification fast path).
compiler: a C++ compiler front end with context-sensitive tokens and precise recovery.
dsl: a small configuration DSL, lexer written in an afternoon, performance irrelevant, in Python.
fastgen: a generated lexer for a build tool where speed matters and rules change rarely.
classic: a teaching compiler that generates its lexer tables from a rule file.
json: skip long runs of whitespace and string bytes in a JSON parser.
compiler, dsl, fastgen, classic, jsoncomb-vector-lookup · mapping · 1 pt · 05-lexer-implementation-stylesA comb-vector table (Definition 1.5.2) with classes 0–2: base = [0, 1, 4]; next = [1, 2, 2, 1, 0]; check = [0, 0, 1, 1, 2]. \(T(q, c) = next[base[q] + c]\) if the index is in range and \(check[base[q] + c] = q\), else dead.
Give T for the keys q1c1, q1c0, q2c0, q0c2 (qXcY means state X, class Y).
q1c1, q1c0, q2c0, q0c2direct-coded-labels · number · 1 pt · 05-lexer-implementation-stylesre2c turns each state of the minimized lexer DFA into one label (the dead state needs none). How many labels does it need for the rules =, ==, !=, [a-z]+?
combinator-first-match · sequence · 1 pt · 05-lexer-implementation-stylesA combinator lexer tries the alternation =|==|<|<=|[a-z]+ with a backtracking engine at each position, takes the FIRST alternative that matches (not the longest), skips nothing else. Give the lexemes it produces for a<=b==c.
simd-starts-mask · set · 1 pt · 05-lexer-implementation-stylesThe 16-byte block id = x2+foo(bar) is classified with SIMD masks; W has bit i set iff byte i is a letter or digit. Which bit positions (0-based) are set in starts = W & ~(W << 1) (no carry from a previous block)?
munch-tokens · sequence · 1 pt · 06-disambiguationRules, in priority order: kw_if if; id [a-z][a-z0-9]*; int [0-9]+; float [0-9]+\.[0-9]+; dotdot \.\.; dot \.; ws +. Give the token rule names produced by maximal munch for if1..2 iffy 3.4.5.
llvm-lex-pptoken-exception · single · 1 pt · 06-disambiguationC++ [lex.pptoken]p3: std::vector<::std::string>. In clang/lib/Lex/Lexer.cpp (LLVM 23.1.2), case '<' sees <:: followed by a character that is neither : nor >. What does it return?
l_square(the digraph<:) followed bycolonless, leaving::for the next tokenlessless- An error:
<::is ill-formed
priority-order · single · 1 pt · 06-disambiguationA flex specification lists the identifier rule [a-z]+ BEFORE the rule while. What happens?
whileis a keyword: longer rules win tieswhileis a keyword: the more specific rule always winswhileis an identifier, and flex warns that the keyword rule cannot be matched- It is a syntax error in the specification
reps-reads · mapping · 1 pt · 06-disambiguationRules a and a*b on the input \(a^{10}\) (no b). A read is one byte fed to the DFA (including the byte that kills it).
naive: reads made by naive maximal munch with backup.
reps: reads made by Reps' memoized maximal munch.
naive, repsmunch-quadratic · number · 1 pt · 06-disambiguationWith the same rules (a, a*b), how many reads does naive maximal munch make on \(a^{20}\)?
interp-kinds · sequence · 1 pt · 07-context-sensitive-lexingGive the Pebble token kinds for the source "x\(a(b))\("c")!" (one string with two interpolations).
interp-depth · number · 1 pt · 07-context-sensitive-lexingWhile lexing the Pebble source "\("\("\(x)")")", what is the maximum height of the InterpolationDepth stack?
js-slash · mapping · 1 pt · 07-context-sensitive-lexingFor each JavaScript snippet, is the first / the start of a regex literal or a division (answer regex or division)?
a: x = a / b / g
b: x = /b/g
c: f(a) / 2
d: if (a) /b/.test(s)
e: return /x/
a, b, c, d, econtext-feedback · mapping · 1 pt · 07-context-sensitive-lexingWhere does the information that resolves each case live? Answer lexer (state kept inside the lexer), parser (the parser decides or re-splits) or symbols (the symbol table).
interpolation: Pebble string interpolation.
slash: JavaScript regex vs division.
angle: C++ >> closing two templates.
indent: Python INDENT/DEDENT.
raw: C++ raw-string delimiters.
typedef: C typedef names.
interpolation, slash, angle, indent, raw, typedefllvm-template-greater · multi · 1 pt · 07-context-sensitive-lexingIn clang/lib/Parse/ParseTemplate.cpp (LLVM 23.1.2), Parser::ParseGreaterThanInTemplateList ends a template argument list. Which tokens does it accept there, splitting off one > when the token is longer? (Ignore CUDA.)
>>>>=>>=->
indent-dedent-count · mapping · 1 pt · 07-context-sensitive-lexingPython's tokenizer on this file (4-space steps):
if a:
if b:
x
z
indent: how many INDENT tokens? dedent: how many DEDENT tokens? before-z: how many DEDENT tokens come immediately before z?
indent, dedent, before-zraw-string-end · number · 1 pt · 07-context-sensitive-lexingC++ source: R"ab(x)a)" )ab" y. How many bytes long is the raw string literal token (from R to its closing quote)?
lexer-hack · single · 1 pt · 07-context-sensitive-lexingC: typedef int T; then, inside void g(int T) { T * a; }. What must the lexer hack classify T as, and how is the statement parsed?
- TYPE_NAME:
ais declared asT * - IDENTIFIER: the statement is a multiplication
- TYPE_NAME, and the declaration of
aconflicts with the parameter - The lexer cannot decide; the statement is ambiguous in C
llvm-keyword-status · mapping · 1 pt · 08-keyword-recognitionIn clang/lib/Basic/IdentifierTable.cpp (LLVM 23.1.2):
function: the free function that decides whether a keyword is a keyword under the given LangOptions.
disabled: the enumerator it returns when the keyword is not a keyword in this language mode.
function, disabledkeyword-choice · mapping · 1 pt · 08-keyword-recognitionPick a keyword-recognition method for each case: hash (pre-interned in the identifier table), gperf (perfect hash), dfa (keywords compiled into the lexer DFA), length (switch on length).
clang: keywords depend on the language mode, and identifiers are interned anyway.
flex: a generated lexer from a rule file.
small: a hand-written lexer with 21 fixed keywords and no interning.
fixedset: a large fixed set of names checked in a C library, with no dynamic changes.
clang, flex, small, fixedsetgperf-hash-value · mapping · 1 pt · 08-keyword-recognitiongperf's hash for Pebble's keywords is \(h(w) = \lvert w \rvert + a[w_2] + a[w_1]\) with \(a[\texttt{m}] = 15\), \(a[\texttt{u}] = 10\), \(a[\texttt{x}] = 5\) (among others); mut has \(h = 28\).
h: the hash of the identifier mux.
slot: the keyword stored in that slot.
rejected-by: what rejects mux (first-char or strcmp)?
h, slot, rejected-bytrie-nodes · number · 1 pt · 08-keyword-recognitionHow many nodes, including the root, does the trie of the keywords {if, in, int, fn, for} have?
switch-length-compares · mapping · 1 pt · 08-keyword-recognitionPebble's switch-on-length keyword lookup (lengths 2: as, fn, if, in; 3: for, int, let, mut, str, var; 4: bool, else, true; 5: break, false, float, while; 6: extern, return, struct; 8: continue). How many string comparisons does it make for each non-keyword identifier?
foo, x, whilex, goutf8-invalid-count · number · 1 pt · 09-unicodeHow many U+FFFD replacement characters does a strict decoder that replaces each maximal subpart (Definition 1.9.4) produce for the bytes 61 E0 80 AF 62 F4 90 80 80 63 E2 82?
llvm-utf8-length · mapping · 1 pt · 09-unicodeIn llvm/lib/Support/ConvertUTF.cpp (LLVM 23.1.2), what does getNumBytesForUTF8 return for each first byte?
0xF4, 0xF5, 0xC0, 0xFCxid-classes · mapping · 1 pt · 09-unicodeClassify each code point as start (XID_Start, hence also XID_Continue), continue (XID_Continue only) or neither.
U+00E9, U+0301, U+0663, U+2211, U+03C0unicode-policy · mapping · 1 pt · 09-unicodeWhich policy does each language apply to non-ASCII identifiers? Answer nfc (UAX #31 identifiers, compared after NFC), nfkc (compared after NFKC), or ascii (identifiers must be ASCII; non-ASCII is diagnosed).
rust: Rust.
python: Python 3.
pebble: Pebble.
rust, python, pebblenfc-nfkc · mapping · 1 pt · 09-unicodeFor each pair, are the two strings equal after normalization? Answer both, nfkc-only or neither.
e-acute: U+00E9 vs e + U+0301.
fi-lig: U+FB01 (fi) vs fi.
cyr-a: Latin a vs Cyrillic U+0430.
sup2: x² vs x2.
angstrom: U+212B ANGSTROM SIGN vs U+00C5.
e-acute, fi-lig, cyr-a, sup2, angstromtrivia-count · mapping · 1 pt · 10-lossless-and-incremental-lexingPebble source (⏎ is one newline byte): x /* a */ = 1 // c⏎;. Give LeadingTrivia of each token: x, eq (=), one (1), semi (;), eof.
x, eq, one, semi, eofroslyn-trailing · mapping · 1 pt · 10-lossless-and-incremental-lexingRoslyn's rule (Definition 1.10.2): a token's trailing trivia runs up to and including the first line break; the rest is the next token's leading trivia. For a; // c⏎ b (⏎ = one newline byte), how many bytes are semi-trailing (after ;) and b-leading (before b)?
semi-trailing, b-leadingtrivia-tokens-count · number · 1 pt · 10-lossless-and-incremental-lexingA lexer that returns trivia as tokens (like rustc_lexer: whitespace runs, line comments without their newline, and ordinary tokens) lexes let x = 1; // c⏎ (⏎ = one newline). How many tokens does it return (no eof)?
llvm-keep-comments · text · 1 pt · 10-lossless-and-incremental-lexingIn clang/include/clang/Lex/Lexer.h (LLVM 23.1.2), which member function of Lexer reports whether comments should be returned as tokens?
incremental-first-token · text · 1 pt · 10-lossless-and-incremental-lexingOld Pebble source let a = b<c; (bytes 0–11). The edit inserts = at byte 10 (between < and c), giving b<=c. Using Definition 1.10.4 (lookahead extent = one past the last byte examined, including the byte that ended the token), which old token is the first affected token? Give its spelling.
incremental-worst-case · single · 1 pt · 10-lossless-and-incremental-lexingWhy can a single one-byte edit force an incremental relexer to relex the rest of the file?
- It can open a comment or string, changing the lexer state at every later boundary so resynchronization never succeeds
- Token positions after the edit must all be shifted by Δ, which is a relex
- The lookahead extents of all tokens must be recomputed
- Incremental relexing is only correct for edits at the end of the file