AgentSkillsCN

operational-semantics-definer

为编程语言定义操作语义。适用场景:(1) 设计新语言;(2) 证明关于语言语义的性质;(3) 实现解释器。

SKILL.md
--- frontmatter
name: operational-semantics-definer
description: 'Defines operational semantics for programming languages. Use when: (1)
  Designing a new language, (2) Proving properties about language semantics, (3) Implementing
  interpreters.'
version: 1.0.0
tags:
- semantics
- operational-semantics
- pl-foundations
- language-design
difficulty: intermediate
languages:
- ocaml
- coq
- python
dependencies: []

Operational Semantics Definer

Defines small-step and big-step operational semantics for programming languages.

When to Use

  • Designing new programming languages
  • Formalizing language semantics
  • Proving properties about programs
  • Implementing interpreters from specs

What This Skill Does

  1. Defines syntax - BNF/EBNF for terms and values
  2. Specifies semantics - Structural operational semantics (SOS)
  3. Proves properties - Progress and preservation
  4. Generates interpreters - Executable semantics

Implementation

Big-Step Interpreter

python
@dataclass
class Store:
    """Memory store: variable -> value"""
    data: dict
    
    def __getitem__(self, x):
        return self.data[x]
    
    def __setitem__(self, x, v):
        return Store({**self.data, x: v})

def eval_expr(e: Expr, s: Store) -> Value:
    match e:
        case Const(n): return n
        case True_(): return True
        case False_(): return False
        case Add(e1, e2):
            v1 = eval_expr(e1, s)
            v2 = eval_expr(e2, s)
            return v1 + v2
        case Eq(e1, e2):
            v1 = eval_expr(e1, s)
            v2 = eval_expr(e2, s)
            return v1 == v2
        case Not(e):
            return not eval_expr(e, s)

def eval_cmd(c: Command, s: Store) -> Store:
    match c:
        case Skip():
            return s
        case Assign(x, e):
            v = eval_expr(e, s)
            return Store({**s.data, x: v})
        case Seq(c1, c2):
            s1 = eval_cmd(c1, s)
            return eval_cmd(c2, s1)
        case If(e, c1, c2):
            if eval_expr(e, s):
                return eval_cmd(c1, s)
            else:
                return eval_cmd(c2, s)
        case While(e, c):
            if eval_expr(e, s):
                s1 = eval_cmd(c, s)
                return eval_cmd(While(e, c), s1)  # Recursive
            else:
                return s

Small-Step Reducer

python
from typing import Callable

# Configuration: (command, store)
Config = tuple[Command, Store]

def reduce_step(c: Command, s: Store) -> Optional[Config]:
    """Take one step, or return None if stuck"""
    
    match c:
        # E-Contx
        case Seq(Skip(), c2):
            return (c2, s)
        
        case Seq(c1, c2):
            if can_step(c1):
                c1', s1 = step(c1)
                return (Seq(c1', c2), s1)
        
        # E-Assign
        case Assign(x, e) if is_value(e):
            return (Skip(), Store({**s.data, x: eval_expr(e, s)}))
        
        # E-AssignCtx
        case Assign(x, e) if not is_value(e):
            e' = step_expr(e)
            return (Assign(x, e'), s)
        
        # E-IfTrue/False
        case If(e, c1, c2) if is_value(e):
            if eval_expr(e, s):
                return (c1, s)
            else:
                return (c2, s)
        
        # E-While
        case While(e, c):
            return (If(e, Seq(c, While(e, c)), Skip()), s)
    
    return None  # Stuck (no rule applies)

def run_small_step(c0: Command, s0: Store, max_steps=1000):
    """Run program to completion"""
    c, s = c0, s0
    for _ in range(max_steps):
        if is_value(c) and isinstance(c, Skip):
            return s  # Done
        result = reduce_step(c, s)
        if result is None:
            raise RuntimeError(f"Stuck at: {c}")
        c, s = result
    raise RuntimeError("Too many steps")

Key Concepts

Big-Step (Natural) Semantics

  • Judgment: ⟨e, s⟩ ⇓ v (expression e in store s evaluates to value v)
  • Pros: Easier to read, direct connection to interpreters
  • Cons: Doesn't express non-termination well

Small-Step (Structural) Semantics

  • Judgment: ⟨e, s⟩ → ⟨e', s'⟩ (one step of evaluation)
  • Pros: Handles non-termination, parallelism, intermediate states
  • Cons: More rules, can be complex

Context Rules

Use evaluation contexts to reduce syntactic clutter:

code
Evaluation contexts:
  E = [] | E + e | v + E | not E | if E then c1 else c2
  
  ⟨E[e], s⟩ → ⟨E[e'], s'⟩  if  ⟨e, s⟩ → ⟨e', s'⟩

Tips

  • Define values first (what cannot step further)
  • Use naming conventions: (⇒) for big-step, (→) for small-step
  • Prove progress + preservation for soundness
  • Use contexts to simplify small-step rules
  • Consider weak vs strong bisimulation

Properties to Prove

Progress

If ⊢ e : τ and e is not a value, then there exists e' such that e → e'

Preservation

If ⊢ e : τ and e → e', then ⊢ e' : τ

Related Skills

  • denotational-semantics-builder - Denotational models
  • hoare-logic-verifier - Axiomatic semantics
  • lambda-calculus-interpreter - Lambda calculus semantics

Canonical References

ReferenceWhy It Matters
Gordon, "Mechanising Programming Languages"POPL courses at Cambridge
Reynolds, "Theories of Programming Languages"Comprehensive semantics text
Winskel, "The Formal Semantics of Programming Languages"Standard textbook
Plotkin, "A Structural Approach to Operational Semantics"Original structural operational semantics
Pierce, "Types and Programming Languages"Chapter 3 covers operational semantics

Tradeoffs and Limitations

Semantic Approach Tradeoffs

ApproachProsCons
Big-step (natural)Intuitive, interpreter-likeNo non-termination, no intermediate states
Small-step (SOS)Handles non-termination, parallelismMore rules, can explode
DenotationalCompositional, algebraic reasoningHarder to prove properties

When NOT to Use Operational Semantics

  • For verification: Use axiomatic (Hoare) or denotational semantics
  • For infinite states: Consider pushdown automata or abstract machines
  • For real-time/continuous: Use timed automata or hybrid systems

Limitations

  • Non-termination: Bigstep can't express diverging computations; use small-step
  • State explosion: Small-step can have many intermediate configurations
  • Parallelism: Hard to express concurrent semantics (use process calculi)
  • Context rules: Required for tractable small-step semantics

Research Tools & Artifacts

Formal semantics in proof assistants:

FormalizationProof AssistantWhat's Formalized
POPLMarkCoqCore lambda calculus, properties
Mechanized Semantics LibraryCoqMultiple languages
SEtPCoqStack-based languages
** Ott**Coq/Isabelle/AgdaLanguage definitions
LemCoq/IsabelleGenerative definitions

Interactive Tools

  • PLT Redex (Racket) - Semantic definitions, visualization
  • K-framework - Language definitions, verification
  • Maude - Rewriting logic semantics

Research Frontiers

1. Parametricity

  • Goal: Prove properties from typing alone
  • Technique: Relational parametricity (Reynolds)
  • Papers: "Types, Abstraction and Parametric Polymorphism" (Wadler)
  • Application: Free theorems

2. Contextual Equivalence

  • Goal: Formalize when programs behave the same
  • Technique: Applicative bisimilarity, environmental bisimilarity
  • Papers: "Environmental Bisimulations" (Sangiorgi & others)

3. Relational Reasoning

  • Goal: Prove relations between programs
  • Technique: Logical relations, step-indexed methods
  • Papers: "Logical Relations for Monadic State" (Birkedal & others)

4. Denotational Semantics Integration

  • Goal: Connect operational and denotational
  • Technique: Full abstraction proofs
  • Papers: "Full Abstraction for PCF" (Milner, Plotkin, etc.)

Implementation Pitfalls

PitfallReal ExampleSolution
Stuck statesSemantically invalid final statesDefine values first
Non-terminationBig-step misses non-terminationUse small-step or coinduction
Non-confluenceDifferent reduction orders give different resultsProve Church-Rosser
Missing contextsRules don't cover all casesUse completeness checking
Store passingMutable state requires careDefine store explicitly

The "Store" Problem

When adding mutable state, must define store explicitly:

python
# Configuration includes store
Configuration = (Command, Store)  # Not just Command

# Evaluation contexts for stores
def eval_cmd(c, s):
    match c:
        case Assign(x, e):
            v = eval_expr(e, s)
            return (Skip(), s[x := v])  # Return new store!

The "Big-Step Non-Termination" Gap

Big-step semantics cannot express divergence:

python
# This loop never produces a result in big-step:
# while true: skip
# No rule produces a value!

# In small-step, we can model it:
# (while true: skip) → (while true: skip)  -- infinite path

This is why we need small-step for non-terminating programs!