Ran Wei/CS Series/13
中文
Computer Science Fundamentals — Ran Wei

Module 13: Languages and limits of computation

Treat a program as structured input: recognise strings, parse expressions and evaluate syntax trees. Then distinguish hard problems from impossible general algorithms.

≈ 5 hours4 sessions2 labs8 exercises6 quiz questions

By the end you can

  • Model a finite automaton.
  • Separate lexing, parsing and evaluation.
  • Explain precedence with a grammar.
  • Outline the halting contradiction.
  • Distinguish P, NP and undecidability.

Before you start

Recommended modules: 04, 07, 08.

Know recursion, proofs and complexity. The interpreter recognises only its stated small grammar; it never calls Python eval.

Contents

Study plan

5 hours

Four 75-minute sessions including practice. Allow longer for extensions or unfamiliar prerequisites. Progress is saved locally and shared between language editions.

Session 175 min
Concepts and worked examples
Session 375 min
Apply and extend
1

Languages and finite automata

A formal language is a set of strings over an alphabet. A deterministic finite automaton has states, a start state, a transition for each symbol and accepting states. For binary strings with an even count of ones, use even and odd states: zero keeps the state; one toggles it; accept even. Empty input is accepted. Finite memory suffices for parity, but arbitrary balanced parentheses require an unbounded nesting count or stack, which a finite automaton cannot generally provide.

Check your understanding

What state follows 101?

Worked solution

Even: two ones toggle twice.

2

Tokens, grammar and syntax trees

Lexing groups characters into tokens such as integers and operators. Parsing checks how tokens fit a grammar and constructs a syntax tree. Our grammar is expression=term ('+' term)*; term=factor ('*' factor)*; factor=integer or '(' expression ')'. Multiplication binds tighter because terms absorb products before expressions combine sums. The tree for 2+3*4 is +(2,*(3,4)), not *(+(2,3),4). A parser must consume the entire input; accepting a valid prefix while ignoring extra tokens is a bug.

Check your understanding

Why does 2+3*4 evaluate to 14?

Worked solution

The grammar groups 3*4 as one term before addition.

3

Interpreters and compilers

An interpreter executes the meaning of a representation; a compiler translates it to another representation. An AST evaluator recursively computes child values and applies their operator. Syntax validity is distinct from semantic validity: a language with division could parse 1/0 yet fail during evaluation. Real language systems may compile to bytecode and then interpret or JIT-compile it, so the categories can coexist. Error reports should identify whether tokenisation, structure or execution failed.

Check your understanding

Is a successful parse proof that evaluation will succeed?

Worked solution

No; semantic errors may remain.

4

Computability and the halting problem

A decision procedure must return yes or no and terminate for every valid input. Suppose H(P,x) always decides whether program P halts on input x. Construct D(P): if H(P,P) says halts, loop forever; otherwise halt. Ask what D(D) does. If H says it halts, D loops; if H says it loops, D halts. Either answer contradicts H's claimed correctness. Therefore no such general halting decider exists in a computational model able to express this construction.

This does not forbid proving termination for particular programs, or deciding restricted finite systems. A time limit is not a general decider: exceeding it cannot distinguish a long terminating run from an infinite one.

Check your understanding

Can a timeout prove a general program never halts?

Worked solution

No; it may halt after the timeout.

5

P, NP and reductions

P contains decision problems solvable in polynomial time in encoded input length. NP contains decision problems whose yes answers have polynomial-sized certificates verifiable in polynomial time. NP does not mean 'not polynomial', and P is contained in NP. For a route-length decision problem, a proposed route can serve as a certificate under suitable bounds. NP-complete problems are in NP and every NP problem reduces to them in polynomial time. A reduction maps instances while preserving yes/no answers. P versus NP remains open; NP-hardness differs fundamentally from undecidability.

Check your understanding

Does NP mean no polynomial-time algorithm exists?

Worked solution

No; P⊆NP, and P versus NP is unresolved.

6

Common misconceptions

  • Parsing is not the same as executing arbitrary host-language code.
  • Undecidable and computationally expensive are different claims.
7

Lab setup

Download each script and run it in a terminal with Python 3.11 or later: python m13_automaton.py. On Windows, py -3 is an alternative; on some systems use python3. The labs use only the standard library. Predict the result before running, then complete the variations. Run without -O so assertions remain enabled. Outputs below were captured by the builder. Code and output are identical in both language editions.

8

Lab 1 — Recognise a language

A two-state machine accepts even parity and rejects symbols outside its alphabet.

Download m13_automaton.py

"""DFA recognising binary strings containing an even number of ones."""
def accepts(text):
    state = 0
    for symbol in text:
        if symbol not in "01": raise ValueError("binary alphabet required")
        if symbol == "1": state = 1 - state
    return state == 0

for text in ["", "0", "1", "11", "101", "111"]:
    result = accepts(text)
    print(repr(text), "accepted:", result)
    assert result == (text.count("1") % 2 == 0)
try:
    accepts("102")
except ValueError:
    print("invalid symbol rejected")
Captured output
'' accepted: True
'0' accepted: True
'1' accepted: False
'11' accepted: True
'101' accepted: True
'111' accepted: False
invalid symbol rejected
  1. Change acceptance to odd parity.
  2. Draw the transition table.
  3. Add a second state component for whether any zero has appeared.
Worked solution

Accept state 1 for odd parity, making empty input reject. On 0, both parity states stay; on 1, they swap. A seen-zero Boolean combines with parity into at most four states.

9

Lab 2 — An expression interpreter

Trace tokenisation, AST construction and evaluation separately. Input length and nesting are bounded.

Download m13_interpreter.py

"""A bounded recursive-descent interpreter: integers, +, *, parentheses."""
import re
def tokens(source):
    if len(source) > 200: raise ValueError("expression too long")
    result, position = [], 0
    while position < len(source):
        if source[position].isspace(): position += 1; continue
        match = re.match(r"[0-9]+|[+*()]", source[position:])
        if not match: raise ValueError("invalid character")
        result.append(match.group()); position += len(match.group())
    return result

class Parser:
    def __init__(self, source):
        self.items, self.position = tokens(source), 0
    def peek(self):
        return self.items[self.position] if self.position < len(self.items) else None
    def take(self):
        value = self.peek()
        if value is None: raise ValueError("unexpected end")
        self.position += 1; return value
    def expression(self, depth=0):
        node = self.term(depth)
        while self.peek() == "+":
            self.take(); node = ("+", node, self.term(depth))
        return node
    def term(self, depth):
        node = self.factor(depth)
        while self.peek() == "*":
            self.take(); node = ("*", node, self.factor(depth))
        return node
    def factor(self, depth):
        if depth > 20: raise ValueError("nesting too deep")
        token = self.take()
        if token == "(":
            node = self.expression(depth+1)
            if self.take() != ")": raise ValueError("missing closing parenthesis")
            return node
        if not token.isdecimal(): raise ValueError("integer expected")
        return int(token)
    def parse(self):
        node = self.expression()
        if self.peek() is not None: raise ValueError("trailing input")
        return node

def evaluate(node):
    if isinstance(node, int): return node
    op, left, right = node
    a, b = evaluate(left), evaluate(right)
    return a+b if op == "+" else a*b

for source in ["2+3*4", "(2+3)*4", "7"]:
    tree = Parser(source).parse()
    print(source, "tree:", tree, "value:", evaluate(tree))
assert evaluate(Parser("2+3*4").parse()) == 14
for source in ["", "2+", "(2+3", "2 3", "__import__('os')"]:
    try: Parser(source).parse()
    except ValueError: print(repr(source), "rejected")
    else: raise AssertionError("invalid expression accepted")
Captured output
2+3*4 tree: ('+', 2, ('*', 3, 4)) value: 14
(2+3)*4 tree: ('*', ('+', 2, 3), 4) value: 20
7 tree: 7 value: 7
'' rejected
'2+' rejected
'(2+3' rejected
'2 3' rejected
"__import__('os')" rejected
  1. Evaluate 2*(3+4*5).
  2. Try unmatched parentheses and trailing tokens.
  3. Design subtraction's grammar and evaluation while preserving left associativity.
Worked solution

The value is 46. Syntax errors are rejected before evaluation. Add '-' to tokens and the expression loop; build ('-',old,right) each step and evaluate subtraction explicitly. 10−3−2 must mean (10−3)−2=5.

10

Exercises with worked solutions

Try before opening the solution. ★ applies an idea; ★★ combines ideas; ★★★ asks for design or proof.

Exercise 1 — DFA★

Does 1111 pass the even-one machine?

Worked solution

Yes, four toggles return to even.

Exercise 2 — AST★

Give the tree for (2+3)*4.

Worked solution

('*', ('+',2,3),4); its value is 20.

Exercise 3 — Prefix acceptance★★

Why reject '2 3' rather than return 2?

Worked solution

It contains unconsumed tokens; the contract accepts complete expressions, not valid prefixes.

Exercise 4 — Semantic error★★

Give an expression syntactically valid but failing in a language with integer division.

Worked solution

1/0 can fit the grammar but division by zero is invalid.

Exercise 5 — Finite restrictions★★

Can a machine recognise balanced parentheses with depth at most two?

Worked solution

Yes, finite states for depths 0,1,2 plus rejection suffice. The unbounded version needs unbounded memory.

Exercise 6 — Halting contradiction★★★

Explain both cases of D(D) in the assumed halting decider proof.

Worked solution

If H predicts halt, D follows the loop branch and does not halt. If H predicts no halt, D returns. Both contradict the prediction, so the universal H cannot exist.

Exercise 7 — Certificate★★★

For 'is there a simple route using at most k edges?', describe a certificate verifier.

Worked solution

Check listed endpoints, no repeated vertex, each consecutive edge exists and length≤k. A simple route has at most V vertices, giving a polynomial certificate and verification.

Exercise 8 — Limits★★

Distinguish NP-complete from undecidable.

Worked solution

NP-complete problems have polynomial verifiers and can be decided by finite exhaustive search, though no general polynomial solution is known. Undecidable problems have no total correct general decision procedure in the model.

11

Self-check quiz

Choose an answer for feedback; reset to retry. A text answer key is available without JavaScript.

1

Lexing produces?

2

Why multiplication binds tighter?

3

An interpreter must use eval?

4

The halting result forbids?

5

NP stands for?

6

P versus NP is?

Answer key
  1. A — Parsing consumes tokens into structure.
  2. B — Grammar defines precedence.
  3. C — Explicit AST evaluation suffices.
  4. A — Specific restricted cases can be solved.
  5. B — It also has a polynomial-verifier characterisation.
  6. C — P is a subset of NP; equality is open.
12

Guided reading

  • MIT computation course — Read the automata, undecidability and complexity lecture summaries; keep the three notions separate.
13

Review and the next step

Draw the AST before evaluating and explain the halting contradiction from memory. Module 14 combines the series into a tested catalogue application.

14

Key terms

TermMeaning
GrammarRules for valid syntactic structure.
DeciderA procedure returning a correct yes/no answer and halting on every valid input.
ReductionA transformation preserving the decision answer between problems.