<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Jocer Franquiz</title>
    <description>The latest articles on DEV Community by Jocer Franquiz (@jocerfranquiz).</description>
    <link>https://dev.to/jocerfranquiz</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3840775%2Fb0255955-2a4c-44f4-b967-419ccba4bff1.jpg</url>
      <title>DEV Community: Jocer Franquiz</title>
      <link>https://dev.to/jocerfranquiz</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jocerfranquiz"/>
    <language>en</language>
    <item>
      <title>A Serious (and hype-less) Study Guide on Agents and LLMs</title>
      <dc:creator>Jocer Franquiz</dc:creator>
      <pubDate>Thu, 09 Apr 2026 18:48:19 +0000</pubDate>
      <link>https://dev.to/jocerfranquiz/a-serious-and-hype-less-study-guide-on-agents-and-llms-1k06</link>
      <guid>https://dev.to/jocerfranquiz/a-serious-and-hype-less-study-guide-on-agents-and-llms-1k06</guid>
      <description>&lt;p&gt;A curated set of resources for understanding LLM agent architecture, the control plane, and how to build effective agents, with direct links to every resource.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Recommended path
&lt;/h2&gt;

&lt;p&gt;If you only have a few hours, do these in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Anthropic: &lt;a href="https://www.anthropic.com/engineering/building-effective-agents" rel="noopener noreferrer"&gt;Building effective agents&lt;/a&gt;&lt;/strong&gt; &lt;em&gt;(~1 hour)&lt;/em&gt;
The single best practical overview from people who ship them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lilian Weng: &lt;a href="https://lilianweng.github.io/posts/2023-06-23-agent/" rel="noopener noreferrer"&gt;LLM Powered Autonomous Agents&lt;/a&gt;&lt;/strong&gt; &lt;em&gt;(~1 hour)&lt;/em&gt;
The canonical academic-flavored overview: planning, memory, tool use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://modelcontextprotocol.io/docs/getting-started/intro" rel="noopener noreferrer"&gt;Model Context Protocol intro&lt;/a&gt;&lt;/strong&gt; + &lt;strong&gt;&lt;a href="https://code.claude.com/docs/en/overview" rel="noopener noreferrer"&gt;Claude Code documentation&lt;/a&gt;&lt;/strong&gt; &lt;em&gt;(1–2 hours)&lt;/em&gt;
The control-plane mental model clicks fast once you've read both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skim one framework's "concepts" page&lt;/strong&gt;, &lt;a href="https://docs.langchain.com/oss/python/langgraph/overview" rel="noopener noreferrer"&gt;LangGraph overview&lt;/a&gt; is the densest &lt;em&gt;(30 min)&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dip into papers&lt;/strong&gt; (&lt;a href="https://arxiv.org/abs/2210.03629" rel="noopener noreferrer"&gt;ReAct&lt;/a&gt;, &lt;a href="https://arxiv.org/abs/2303.11366" rel="noopener noreferrer"&gt;Reflexion&lt;/a&gt;, …) only when a specific pattern catches your interest.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  2. Foundational essays: read these first
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.anthropic.com/engineering/building-effective-agents" rel="noopener noreferrer"&gt;&lt;em&gt;Building effective agents&lt;/em&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Erik Schluntz &amp;amp; Barry Zhang, Anthropic, December 2024.&lt;/strong&gt; The best practical overview. Covers workflows vs agents, common patterns (prompt chaining, routing, parallelization, orchestrator-workers, evaluator-optimizer), and (crucially) when &lt;em&gt;not&lt;/em&gt; to use an agent. The companion code lives in the &lt;a href="https://github.com/anthropics/claude-cookbooks/tree/main/patterns/agents" rel="noopener noreferrer"&gt;Claude Cookbooks agent patterns folder&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://lilianweng.github.io/posts/2023-06-23-agent/" rel="noopener noreferrer"&gt;&lt;em&gt;LLM Powered Autonomous Agents&lt;/em&gt;&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Lilian Weng (OpenAI), June 2023.&lt;/strong&gt; The canonical academic-flavored overview: planning, memory, tool use. Still the most-cited single piece in the field. Lives on her blog &lt;a href="https://lilianweng.github.io/" rel="noopener noreferrer"&gt;Lil'Log&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://www.oreilly.com/library/view/ai-engineering/9781098166298/" rel="noopener noreferrer"&gt;&lt;em&gt;AI Engineering&lt;/em&gt;&lt;/a&gt; (chapter on Agents)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Chip Huyen, O'Reilly, 2024.&lt;/strong&gt; Excellent on the engineering side: evaluation, failure modes, planning loops. The whole book is worth owning. See also &lt;a href="https://huyenchip.com/books/" rel="noopener noreferrer"&gt;Chip Huyen's books page&lt;/a&gt; and the &lt;a href="https://github.com/chiphuyen/aie-book" rel="noopener noreferrer"&gt;supporting GitHub repository&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Patterns &amp;amp; techniques: the original papers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Paper&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Key idea&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://arxiv.org/abs/2210.03629" rel="noopener noreferrer"&gt;&lt;strong&gt;ReAct&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Yao et al., 2022&lt;/td&gt;
&lt;td&gt;Interleave Thought → Action → Observation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://arxiv.org/abs/2303.11366" rel="noopener noreferrer"&gt;&lt;strong&gt;Reflexion&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Shinn et al., 2023&lt;/td&gt;
&lt;td&gt;Self-critique to improve over iterations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://arxiv.org/abs/2302.04761" rel="noopener noreferrer"&gt;&lt;strong&gt;Toolformer&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Schick et al., 2023&lt;/td&gt;
&lt;td&gt;Tool use as a learned skill&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://arxiv.org/abs/2305.10601" rel="noopener noreferrer"&gt;&lt;strong&gt;Tree of Thoughts&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Yao et al., 2023&lt;/td&gt;
&lt;td&gt;Explicit search over reasoning branches&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://arxiv.org/abs/2305.04091" rel="noopener noreferrer"&gt;&lt;strong&gt;Plan-and-Solve&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Wang et al., 2023&lt;/td&gt;
&lt;td&gt;Decompose first, then execute step by step&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://arxiv.org/abs/2305.16291" rel="noopener noreferrer"&gt;&lt;strong&gt;Voyager&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Wang et al., 2023&lt;/td&gt;
&lt;td&gt;Skill libraries / procedural memory in the wild (&lt;a href="https://voyager.minedojo.org/" rel="noopener noreferrer"&gt;project site&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://arxiv.org/abs/2303.17651" rel="noopener noreferrer"&gt;&lt;strong&gt;Self-Refine&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Madaan et al., 2023&lt;/td&gt;
&lt;td&gt;Iterative improvement via self-feedback (&lt;a href="https://selfrefine.info/" rel="noopener noreferrer"&gt;project site&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://arxiv.org/abs/2201.11903" rel="noopener noreferrer"&gt;&lt;strong&gt;Chain-of-Thought&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Wei et al., 2022&lt;/td&gt;
&lt;td&gt;Step-by-step reasoning prompts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://arxiv.org/abs/2304.03442" rel="noopener noreferrer"&gt;&lt;strong&gt;Generative Agents&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Park et al., 2023&lt;/td&gt;
&lt;td&gt;The famous Smallville simulation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  4. Protocols &amp;amp; specs (the control-plane stuff)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;Model Context Protocol (MCP)&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Anthropic's open spec for plugging tool servers into any agent. The de-facto standard for tool interoperability. Start with &lt;a href="https://modelcontextprotocol.io/docs/getting-started/intro" rel="noopener noreferrer"&gt;the introduction&lt;/a&gt; and the &lt;a href="https://github.com/modelcontextprotocol" rel="noopener noreferrer"&gt;main GitHub org&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://agents.md/" rel="noopener noreferrer"&gt;AGENTS.md&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Cross-vendor spec for "instructions to coding agents" files. Originated by OpenAI Codex, Amp, Jules (Google), Cursor, and Factory; now stewarded by the Agentic AI Foundation under the Linux Foundation. Implemented across most coding agents. Source on &lt;a href="https://github.com/agentsmd/agents.md" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://agentskills.io/" rel="noopener noreferrer"&gt;Agent Skills&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Anthropic's open &lt;code&gt;SKILL.md&lt;/code&gt; standard for lazy-loaded capability bundles. olders of instructions, scripts, and resources that an agent discovers via metadata and loads on demand. Originally a Claude Code feature, now adopted by Cursor, GitHub Copilot, VS Code, Gemini CLI, OpenAI Codex, OpenHands, Goose, Letta, JetBrains Junie, Factory, Amp, and ~20 other tools. Start with the &lt;a href="https://agentskills.io/home" rel="noopener noreferrer"&gt;overview&lt;/a&gt;, then the &lt;a href="https://agentskills.io/specification" rel="noopener noreferrer"&gt;specification&lt;/a&gt;. Source on &lt;a href="https://github.com/agentskills/agentskills" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;; Anthropic's example skills at &lt;a href="https://github.com/anthropics/skills" rel="noopener noreferrer"&gt;anthropics/skills&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenAPI → tool schemas
&lt;/h3&gt;

&lt;p&gt;Tool schemas can be auto-generated from OpenAPI specs. Most frameworks support this directly.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Claude Code &amp;amp; Anthropic ecosystem
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://code.claude.com/docs/en/overview" rel="noopener noreferrer"&gt;Claude Code documentation&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;The official source of truth, updates frequently. Sections on hooks, skills, subagents, MCP, settings, slash commands, plugins, output styles, status lines. The mirror at &lt;a href="https://docs.anthropic.com/en/docs/claude-code/overview" rel="noopener noreferrer"&gt;docs.anthropic.com/en/docs/claude-code&lt;/a&gt; also serves the same content. Source on &lt;a href="https://github.com/anthropics/claude-code" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Claude Agent SDK
&lt;/h3&gt;

&lt;p&gt;Same docs site. The SDK exposes the same primitives (tools, hooks, permissions) that Claude Code uses, so reading the SDK docs is one of the fastest ways to understand the harness model.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;a href="https://github.com/anthropics/claude-cookbooks" rel="noopener noreferrer"&gt;Claude Cookbooks&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;Practical agent recipes on GitHub (formerly &lt;em&gt;Anthropic Cookbook&lt;/em&gt;). The &lt;a href="https://github.com/anthropics/claude-cookbooks/tree/main/patterns/agents" rel="noopener noreferrer"&gt;&lt;code&gt;patterns/agents/&lt;/code&gt;&lt;/a&gt; folder contains the reference implementations for &lt;em&gt;Building Effective Agents&lt;/em&gt; (orchestrator-workers, evaluator-optimizer, etc.).&lt;/p&gt;

&lt;h3&gt;
  
  
  Anthropic Engineering blog
&lt;/h3&gt;

&lt;p&gt;Periodic deep dives on agent design, tool use, and prompt engineering. Published under &lt;a href="https://www.anthropic.com/engineering" rel="noopener noreferrer"&gt;anthropic.com/engineering&lt;/a&gt; and &lt;a href="https://www.anthropic.com/research" rel="noopener noreferrer"&gt;anthropic.com/research&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Frameworks (good for "show me code")
&lt;/h2&gt;

&lt;p&gt;Each framework's docs is essentially an opinionated essay on agent architecture. Read the &lt;strong&gt;concepts&lt;/strong&gt; pages, not the API reference.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Framework&lt;/th&gt;
&lt;th&gt;Strength&lt;/th&gt;
&lt;th&gt;Links&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;LangGraph&lt;/strong&gt; (LangChain)&lt;/td&gt;
&lt;td&gt;Stateful loops, multi-agent, human-in-the-loop&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://docs.langchain.com/oss/python/langgraph/overview" rel="noopener noreferrer"&gt;docs&lt;/a&gt; · &lt;a href="https://www.langchain.com/langgraph" rel="noopener noreferrer"&gt;product&lt;/a&gt; · &lt;a href="https://github.com/langchain-ai/langgraph" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LlamaIndex Workflows / Agents&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Retrieval and memory&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://developers.llamaindex.ai/python/framework/use_cases/agents/" rel="noopener noreferrer"&gt;agents docs&lt;/a&gt; · &lt;a href="https://www.llamaindex.ai/blog/announcing-workflows-1-0-a-lightweight-framework-for-agentic-systems" rel="noopener noreferrer"&gt;Workflows 1.0 announcement&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pydantic AI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Typed tool calls, clean mental model&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://ai.pydantic.dev/" rel="noopener noreferrer"&gt;docs&lt;/a&gt; · &lt;a href="https://github.com/pydantic/pydantic-ai" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;smolagents&lt;/strong&gt; (Hugging Face)&lt;/td&gt;
&lt;td&gt;Minimal, code-as-action&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://huggingface.co/docs/smolagents/index" rel="noopener noreferrer"&gt;docs&lt;/a&gt; · &lt;a href="https://github.com/huggingface/smolagents" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://huggingface.co/blog/smolagents" rel="noopener noreferrer"&gt;intro blog&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CrewAI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multi-agent role-based&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://docs.crewai.com/" rel="noopener noreferrer"&gt;docs&lt;/a&gt; · &lt;a href="https://github.com/crewaiinc/crewai" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;AutoGen&lt;/strong&gt; (Microsoft)&lt;/td&gt;
&lt;td&gt;Conversational multi-agent (now in maintenance, see Microsoft Agent Framework below)&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://microsoft.github.io/autogen/stable/index.html" rel="noopener noreferrer"&gt;docs&lt;/a&gt; · &lt;a href="https://github.com/microsoft/autogen" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Microsoft Agent Framework&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The successor to AutoGen, enterprise-ready&lt;/td&gt;
&lt;td&gt;&lt;a href="https://learn.microsoft.com/en-us/agent-framework/overview/" rel="noopener noreferrer"&gt;docs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenAI Agents SDK&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lightweight handoff-based (production successor to Swarm)&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://openai.github.io/openai-agents-python/" rel="noopener noreferrer"&gt;docs&lt;/a&gt; · &lt;a href="https://github.com/openai/openai-agents-python" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://github.com/openai/swarm" rel="noopener noreferrer"&gt;original Swarm&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;DSPy&lt;/strong&gt; (Stanford)&lt;/td&gt;
&lt;td&gt;Programmatic prompts, optimization&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://dspy.ai/" rel="noopener noreferrer"&gt;site&lt;/a&gt; · &lt;a href="https://github.com/stanfordnlp/dspy" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  7. Memory &amp;amp; retrieval
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://microsoft.github.io/graphrag/" rel="noopener noreferrer"&gt;GraphRAG&lt;/a&gt;&lt;/strong&gt; (Microsoft Research, 2024): graph-augmented retrieval over a corpus. &lt;a href="https://github.com/microsoft/graphrag" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://www.microsoft.com/en-us/research/project/graphrag/" rel="noopener noreferrer"&gt;project page&lt;/a&gt; · &lt;a href="https://arxiv.org/abs/2404.16130" rel="noopener noreferrer"&gt;paper&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/letta-ai/letta" rel="noopener noreferrer"&gt;MemGPT / Letta&lt;/a&gt;&lt;/strong&gt;: tiered memory inspired by OS virtual memory. The original &lt;a href="https://arxiv.org/abs/2310.08560" rel="noopener noreferrer"&gt;MemGPT paper (Packer et al., 2023)&lt;/a&gt; is the canonical reference; the modern &lt;a href="https://docs.letta.com/" rel="noopener noreferrer"&gt;Letta framework&lt;/a&gt; is the production successor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vector DB docs&lt;/strong&gt;: &lt;a href="https://qdrant.tech/documentation/" rel="noopener noreferrer"&gt;Qdrant&lt;/a&gt;, &lt;a href="https://docs.weaviate.io/" rel="noopener noreferrer"&gt;Weaviate&lt;/a&gt;, &lt;a href="https://github.com/pgvector/pgvector" rel="noopener noreferrer"&gt;pgvector&lt;/a&gt;, &lt;a href="https://docs.trychroma.com/" rel="noopener noreferrer"&gt;Chroma&lt;/a&gt;: each has good intro material.&lt;/li&gt;
&lt;li&gt;For RAG patterns, the &lt;a href="https://developers.llamaindex.ai/python/framework/use_cases/agents/" rel="noopener noreferrer"&gt;LlamaIndex agents docs&lt;/a&gt; is the canonical reference.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  8. Observability &amp;amp; evaluation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Tracing platforms
&lt;/h3&gt;

&lt;p&gt;Each has docs that double as a tutorial on what to instrument:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://langfuse.com/" rel="noopener noreferrer"&gt;Langfuse&lt;/a&gt;&lt;/strong&gt;: open source, self-hostable. &lt;a href="https://langfuse.com/docs" rel="noopener noreferrer"&gt;Docs&lt;/a&gt; · &lt;a href="https://github.com/langfuse/langfuse" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://smith.langchain.com/" rel="noopener noreferrer"&gt;LangSmith&lt;/a&gt;&lt;/strong&gt;: hosted, by LangChain. &lt;a href="https://docs.langchain.com/langsmith/home" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://phoenix.arize.com/" rel="noopener noreferrer"&gt;Arize Phoenix&lt;/a&gt;&lt;/strong&gt;: open source, very conceptual docs. &lt;a href="https://arize.com/docs/phoenix" rel="noopener noreferrer"&gt;Docs&lt;/a&gt; · &lt;a href="https://github.com/Arize-ai/phoenix" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.helicone.ai/" rel="noopener noreferrer"&gt;Helicone&lt;/a&gt;&lt;/strong&gt;: proxy-based. &lt;a href="https://docs.helicone.ai/" rel="noopener noreferrer"&gt;Docs&lt;/a&gt; · &lt;a href="https://github.com/Helicone/helicone" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.braintrust.dev/" rel="noopener noreferrer"&gt;Braintrust&lt;/a&gt;&lt;/strong&gt;: eval-focused. &lt;a href="https://www.braintrust.dev/docs" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Standards
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/" rel="noopener noreferrer"&gt;OpenTelemetry GenAI semantic conventions&lt;/a&gt;&lt;/strong&gt;: the emerging standard for tracing LLM/agent calls. See also the &lt;a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-agent-spans/" rel="noopener noreferrer"&gt;agent-spans page&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Evaluation frameworks &amp;amp; benchmarks
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/EleutherAI/lm-evaluation-harness" rel="noopener noreferrer"&gt;&lt;code&gt;lm-evaluation-harness&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; (EleutherAI): base-model benchmarks; backend for the HuggingFace Open LLM Leaderboard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://crfm.stanford.edu/helm/" rel="noopener noreferrer"&gt;HELM&lt;/a&gt;&lt;/strong&gt; (Stanford CRFM): holistic evaluation framework. &lt;a href="https://github.com/stanford-crfm/helm" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://arxiv.org/abs/2211.09110" rel="noopener noreferrer"&gt;paper&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/THUDM/AgentBench" rel="noopener noreferrer"&gt;AgentBench&lt;/a&gt;&lt;/strong&gt; (Tsinghua): multi-environment LLM-as-agent benchmark. &lt;a href="https://arxiv.org/abs/2308.03688" rel="noopener noreferrer"&gt;Paper&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.swebench.com/" rel="noopener noreferrer"&gt;SWE-bench&lt;/a&gt;&lt;/strong&gt;: solving real GitHub issues. &lt;a href="https://github.com/SWE-bench/SWE-bench" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/sierra-research/tau-bench" rel="noopener noreferrer"&gt;τ-bench&lt;/a&gt;&lt;/strong&gt; (Sierra): tool-agent-user interaction in real-world domains. &lt;a href="https://sierra.ai/blog/tau-bench-shaping-development-evaluation-agents" rel="noopener noreferrer"&gt;Blog post&lt;/a&gt; · &lt;a href="https://github.com/sierra-research/tau2-bench" rel="noopener noreferrer"&gt;τ²-bench&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  9. Safety, security, and guardrails
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://genai.owasp.org/llm-top-10/" rel="noopener noreferrer"&gt;OWASP Top 10 for LLM Applications&lt;/a&gt;&lt;/strong&gt;: the standard threat list. &lt;a href="https://owasp.org/www-project-top-10-for-large-language-model-applications/" rel="noopener noreferrer"&gt;Project page&lt;/a&gt; · &lt;a href="https://owasp.org/www-project-top-10-for-large-language-model-applications/assets/PDF/OWASP-Top-10-for-LLMs-v2025.pdf" rel="noopener noreferrer"&gt;2025 PDF&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompt injection&lt;/strong&gt;: &lt;a href="https://simonwillison.net/series/prompt-injection/" rel="noopener noreferrer"&gt;Simon Willison's prompt injection series&lt;/a&gt; is the most comprehensive ongoing coverage. He coined the term and continues to write about new variants on his &lt;a href="https://simonwillison.net/" rel="noopener noreferrer"&gt;main blog&lt;/a&gt; and his &lt;a href="https://simonw.substack.com/" rel="noopener noreferrer"&gt;substack&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://www.nist.gov/itl/ai-risk-management-framework" rel="noopener noreferrer"&gt;NIST AI Risk Management Framework&lt;/a&gt;&lt;/strong&gt;: for governance angles. &lt;a href="https://nvlpubs.nist.gov/nistpubs/ai/nist.ai.100-1.pdf" rel="noopener noreferrer"&gt;AI RMF 1.0 PDF&lt;/a&gt; · &lt;a href="https://airc.nist.gov/airmf-resources/airmf/" rel="noopener noreferrer"&gt;Resource Center&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anthropic's Responsible Scaling Policy&lt;/strong&gt; model-level safety thinking, published on &lt;a href="https://www.anthropic.com/" rel="noopener noreferrer"&gt;anthropic.com&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  10. Multi-agent &amp;amp; emerging directions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://arxiv.org/abs/2308.08155" rel="noopener noreferrer"&gt;AutoGen paper&lt;/a&gt;&lt;/strong&gt; (Wu et al., 2023): multi-agent conversation framework&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/FoundationAgents/MetaGPT" rel="noopener noreferrer"&gt;MetaGPT&lt;/a&gt;&lt;/strong&gt;: assembly-line multi-agent. &lt;a href="https://arxiv.org/abs/2308.00352" rel="noopener noreferrer"&gt;Paper&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/OpenBMB/ChatDev" rel="noopener noreferrer"&gt;ChatDev&lt;/a&gt;&lt;/strong&gt;: software-company-as-multi-agent. &lt;a href="https://arxiv.org/abs/2307.07924" rel="noopener noreferrer"&gt;Paper&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/joonspk-research/generative_agents" rel="noopener noreferrer"&gt;Generative Agents&lt;/a&gt;&lt;/strong&gt; (Park et al., 2023): the famous Smallville simulation. &lt;a href="https://arxiv.org/abs/2304.03442" rel="noopener noreferrer"&gt;Paper&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  11. Going deeper: books
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Chip Huyen: &lt;a href="https://www.oreilly.com/library/view/ai-engineering/9781098166298/" rel="noopener noreferrer"&gt;&lt;em&gt;AI Engineering&lt;/em&gt;&lt;/a&gt;&lt;/strong&gt; (O'Reilly, 2024): production AI systems. &lt;a href="https://huyenchip.com/books/" rel="noopener noreferrer"&gt;Author page&lt;/a&gt; · &lt;a href="https://github.com/chiphuyen/aie-book" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jay Alammar &amp;amp; Maarten Grootendorst: &lt;a href="https://www.llm-book.com/" rel="noopener noreferrer"&gt;&lt;em&gt;Hands-On Large Language Models&lt;/em&gt;&lt;/a&gt;&lt;/strong&gt; (O'Reilly, 2024): visual, accessible. &lt;a href="https://www.oreilly.com/library/view/hands-on-large-language/9781098150952/" rel="noopener noreferrer"&gt;O'Reilly&lt;/a&gt; · &lt;a href="https://github.com/HandsOnLLM/Hands-On-Large-Language-Models" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sebastian Raschka: &lt;a href="https://www.manning.com/books/build-a-large-language-model-from-scratch" rel="noopener noreferrer"&gt;&lt;em&gt;Build a Large Language Model (From Scratch)&lt;/em&gt;&lt;/a&gt;&lt;/strong&gt; (Manning, 2024): for understanding what's &lt;em&gt;inside&lt;/em&gt; the LLM. &lt;a href="https://github.com/rasbt/LLMs-from-scratch" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; · &lt;a href="https://sebastianraschka.com/books/" rel="noopener noreferrer"&gt;author's books page&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  12. Communities &amp;amp; ongoing reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Anthropic, OpenAI, DeepMind engineering blogs&lt;/strong&gt;: best practical writing

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://www.anthropic.com/engineering" rel="noopener noreferrer"&gt;Anthropic Engineering&lt;/a&gt; · &lt;a href="https://www.anthropic.com/research" rel="noopener noreferrer"&gt;Anthropic Research&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;&lt;a href="https://simonwillison.net/" rel="noopener noreferrer"&gt;Simon Willison's blog&lt;/a&gt;&lt;/strong&gt;: daily LLM news and analysis (the best single feed in the field)&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;&lt;a href="https://www.latent.space/podcast" rel="noopener noreferrer"&gt;Latent Space podcast&lt;/a&gt;&lt;/strong&gt;: interviews with builders, hosted by swyx and Alessio. &lt;a href="https://www.latent.space/" rel="noopener noreferrer"&gt;Newsletter&lt;/a&gt;
&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Hacker News &lt;code&gt;ai&lt;/code&gt; tag&lt;/strong&gt;: high-signal discussions&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;&lt;a href="https://blog.langchain.dev/" rel="noopener noreferrer"&gt;LangChain blog&lt;/a&gt;&lt;/strong&gt;, &lt;strong&gt;&lt;a href="https://www.llamaindex.ai/blog" rel="noopener noreferrer"&gt;LlamaIndex blog&lt;/a&gt;&lt;/strong&gt;: framework-level pattern writeups&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;arXiv &lt;a href="https://arxiv.org/list/cs.CL/recent" rel="noopener noreferrer"&gt;&lt;code&gt;cs.CL&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://arxiv.org/list/cs.AI/recent" rel="noopener noreferrer"&gt;&lt;code&gt;cs.AI&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;: primary research&lt;/li&gt;

&lt;/ul&gt;




&lt;h2&gt;
  
  
  13. By topic: quick reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If you want to understand…&lt;/th&gt;
&lt;th&gt;Start with&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What an agent is&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.anthropic.com/engineering/building-effective-agents" rel="noopener noreferrer"&gt;Anthropic &lt;em&gt;Building effective agents&lt;/em&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Planning patterns&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://arxiv.org/abs/2210.03629" rel="noopener noreferrer"&gt;ReAct&lt;/a&gt;, &lt;a href="https://arxiv.org/abs/2305.04091" rel="noopener noreferrer"&gt;Plan-and-Solve&lt;/a&gt; papers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Memory architectures&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://lilianweng.github.io/posts/2023-06-23-agent/" rel="noopener noreferrer"&gt;Lilian Weng's post&lt;/a&gt;, &lt;a href="https://github.com/letta-ai/letta" rel="noopener noreferrer"&gt;MemGPT/Letta&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tool integration&lt;/td&gt;
&lt;td&gt;&lt;a href="https://modelcontextprotocol.io/" rel="noopener noreferrer"&gt;MCP docs&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Configuration / control plane&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://code.claude.com/docs/en/overview" rel="noopener noreferrer"&gt;Claude Code docs&lt;/a&gt; (hooks, skills, subagents)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-agent systems&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://docs.langchain.com/oss/python/langgraph/overview" rel="noopener noreferrer"&gt;LangGraph&lt;/a&gt;, &lt;a href="https://microsoft.github.io/autogen/stable/index.html" rel="noopener noreferrer"&gt;AutoGen&lt;/a&gt;, &lt;a href="https://github.com/FoundationAgents/MetaGPT" rel="noopener noreferrer"&gt;MetaGPT&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production tracing&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://arize.com/docs/phoenix" rel="noopener noreferrer"&gt;Arize Phoenix&lt;/a&gt; or &lt;a href="https://langfuse.com/docs" rel="noopener noreferrer"&gt;Langfuse&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent evaluation&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://www.swebench.com/" rel="noopener noreferrer"&gt;SWE-bench&lt;/a&gt;, &lt;a href="https://github.com/sierra-research/tau-bench" rel="noopener noreferrer"&gt;τ-bench&lt;/a&gt;, &lt;a href="https://github.com/THUDM/AgentBench" rel="noopener noreferrer"&gt;AgentBench&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Prompt injection / safety&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://simonwillison.net/series/prompt-injection/" rel="noopener noreferrer"&gt;Simon Willison's series&lt;/a&gt;, &lt;a href="https://genai.owasp.org/llm-top-10/" rel="noopener noreferrer"&gt;OWASP LLM Top 10&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RAG&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://developers.llamaindex.ai/python/framework/use_cases/agents/" rel="noopener noreferrer"&gt;LlamaIndex agents&lt;/a&gt;, &lt;a href="https://microsoft.github.io/graphrag/" rel="noopener noreferrer"&gt;GraphRAG&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLMs from the inside&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.manning.com/books/build-a-large-language-model-from-scratch" rel="noopener noreferrer"&gt;Sebastian Raschka's book&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  14. A note on freshness
&lt;/h2&gt;

&lt;p&gt;This field moves fast. Patterns from 2023 may be obsolete; protocols from 2024 may be standard by next quarter. Treat any specific tool or framework recommendation as a snapshot, not gospel. The &lt;strong&gt;concepts&lt;/strong&gt; (loop, memory, tools, control plane, three knobs) are stable. The &lt;strong&gt;implementations&lt;/strong&gt; churn.&lt;/p&gt;

&lt;p&gt;When in doubt: read the official docs of whatever tool you're actually using, then triangulate with one or two of the foundational essays above.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agentskills</category>
      <category>learning</category>
      <category>llm</category>
    </item>
    <item>
      <title>Why Formal Systems Can't Read Their Own Output?</title>
      <dc:creator>Jocer Franquiz</dc:creator>
      <pubDate>Tue, 07 Apr 2026 18:29:28 +0000</pubDate>
      <link>https://dev.to/jocerfranquiz/why-formal-systems-cant-read-their-own-output-2i3f</link>
      <guid>https://dev.to/jocerfranquiz/why-formal-systems-cant-read-their-own-output-2i3f</guid>
      <description>&lt;p&gt;What do concatenative combinator calculus, mathematicians, and language models have in common? They all hit the same wall: &lt;strong&gt;Evaluation is lossy. A formal system can produce expressions but cannot unambiguously interpret them using only its own rules, because reduction destroys the information needed to recover intent.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This post traces a thread that surprised me, starting from a very concrete implementation question about a stack-based evaluator and ending somewhere between information theory and machine learning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concatenative Combinator evaluation
&lt;/h2&gt;

&lt;p&gt;Imagine a simple concatenative (stack-based) combinator language. We have primitive operations like &lt;code&gt;swap&lt;/code&gt;, &lt;code&gt;drop&lt;/code&gt;, &lt;code&gt;dip&lt;/code&gt;, &lt;code&gt;call&lt;/code&gt;. We can define new words in a dictionary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;true  := [ swap drop ]
false := [ drop ]
not   := [ [ swap drop ] [ drop ] ] dip call
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The evaluator reduces expressions by expanding words into their definitions and applying reduction rules. It works fine. But the thing is, once an expression is fully reduced, we're left staring at a pile of primitives. The word &lt;code&gt;not&lt;/code&gt; has been dissolved into &lt;code&gt;[ [ swap drop ] [ drop ] ] dip call&lt;/code&gt;, and nothing in the output tells us it was ever &lt;code&gt;not&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This is normal. Evaluators reduce. They don't un-reduce.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Reflection Problem
&lt;/h2&gt;

&lt;p&gt;Now suppose we want the system to have a &lt;strong&gt;reflection&lt;/strong&gt; property (programs can inspect their own structure at runtime and check whether some subexpression matches a word in the dictionary). Not as an external &lt;em&gt;prettifier&lt;/em&gt; applied after the fact, but as a capability &lt;em&gt;within&lt;/em&gt; the calculus itself. A program can look at the top of the stack and ask: "do I recognize this?"&lt;/p&gt;

&lt;p&gt;Structural equality checking is straightforward. We walk two expression trees and compare. The real problem emerges when we try it on a concrete example.&lt;/p&gt;

&lt;p&gt;Take this expression sitting on the stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ [ [ swap drop ] [ drop ] ] dip call ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consulting the dictionary, we can read this as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Level 0&lt;/strong&gt;: just primitives: &lt;code&gt;[ [ [ swap drop ] [ drop ] ] dip call ]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Level 1&lt;/strong&gt;: partial recognition: &lt;code&gt;[ [ true false ] dip call ]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Level 2&lt;/strong&gt;: full recognition: &lt;code&gt;[ not ]&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three are correct. The expression &lt;em&gt;is&lt;/em&gt; all of these simultaneously.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8xmptehrxrcxsb316xbj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8xmptehrxrcxsb316xbj.png" alt="One expression, three readings — the same reduced form interpreted at different abstraction levels" width="720" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The matching is easy. The problem is that we get &lt;strong&gt;multiple valid matchings at different levels of abstraction&lt;/strong&gt;, and nothing inside the formal system tells us which one is "right."&lt;/p&gt;

&lt;h2&gt;
  
  
  But this ambiguity is familiar
&lt;/h2&gt;

&lt;p&gt;This is exactly what happens in natural language. "She is cold" means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;She is emotionally less expressive than others.&lt;/li&gt;
&lt;li&gt;Her body temperature is below average.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both readings are syntactically and semantically valid. The sentence is genuinely, irreducibly ambiguous. Linguists have studied this for decades (Grice's cooperative principle, Sperber and Wilson's relevance theory, for example). And the consensus is the same: we resolve it through &lt;strong&gt;context&lt;/strong&gt;, the surrounding conversation, shared knowledge, the situation, pragmatic inference about the speaker's intent. The resolution comes from outside the sentence itself.&lt;/p&gt;

&lt;p&gt;Our combinator expression has the same property. The syntax supports multiple interpretations. The formal system can &lt;em&gt;enumerate&lt;/em&gt; them but cannot &lt;em&gt;choose&lt;/em&gt; between them. The intent of the original author: "did they mean &lt;code&gt;not&lt;/code&gt;, or were they deliberately composing &lt;code&gt;true&lt;/code&gt; and &lt;code&gt;false&lt;/code&gt; with a branching pattern?" was lost during evaluation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evaluation is lossy.&lt;/strong&gt; It destroys authorial intent. Reflection tries to recover the signal that reduction discarded.&lt;/p&gt;

&lt;h3&gt;
  
  
  If the system can't resolve the ambiguity from within, what can?
&lt;/h3&gt;

&lt;p&gt;The obvious engineering answer is: &lt;strong&gt;don't lose the information in the first place.&lt;/strong&gt; Instrument the evaluator to retain provenance, record which dictionary words were expanded at each step, and we can trace back to the original expression deterministically. This works in a closed system where we control the evaluator.&lt;/p&gt;

&lt;p&gt;But it breaks down quickly in practice: expressions arrive from external sources without history, interop boundaries strip annotations, provenance metadata grows combinatorially with expression size, and legacy systems weren't built to carry it. In the general case (reading an expression we didn't produce) the information is gone.&lt;/p&gt;

&lt;p&gt;So what resolves the ambiguity when provenance isn't available? The same thing that resolves "she is cold": &lt;strong&gt;learned preferences shaped by experience.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The approach would be:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The system presents all valid interpretations to the user, ranked by some initial heuristic (longest match, most abstract, most frequently chosen).&lt;/li&gt;
&lt;li&gt;The user reorders them: "no, in this context I meant &lt;code&gt;not&lt;/code&gt;, not &lt;code&gt;[ true false ] branch&lt;/code&gt;."&lt;/li&gt;
&lt;li&gt;A model learns from these supervised signals.&lt;/li&gt;
&lt;li&gt;Over time, the system predicts the preferred interpretation without asking.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is supervised machine learning. More specifically, it's the same setup that underlies language models: given an ambiguous sequence, predict the most probable interpretation based on patterns learned from human-labeled examples.&lt;/p&gt;

&lt;p&gt;We started with combinatory logic and arrived at a setting where a language model is a natural fit.&lt;/p&gt;

&lt;p&gt;Proof tells us what's equivalent. Prediction tells us what was meant.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Information-Theoretic View
&lt;/h2&gt;

&lt;p&gt;Why is this ambiguity fundamental rather than incidental?&lt;/p&gt;

&lt;p&gt;Evaluation is a &lt;strong&gt;many-to-one function&lt;/strong&gt;. Multiple distinct source expressions reduce to the same normal form. &lt;code&gt;not&lt;/code&gt;, &lt;code&gt;[ true false ] branch&lt;/code&gt;, and &lt;code&gt;[ [ swap drop ] [ drop ] ] dip call&lt;/code&gt; all collapse to identical primitive sequences. The mapping from source to reduced form is non-injective, it destroys distinctions.&lt;/p&gt;

&lt;p&gt;Recovering intent from a reduced expression is therefore an &lt;strong&gt;inverse problem&lt;/strong&gt;: given one output, determine which of many possible inputs produced it. This is the same structure we find in lossy compression, decompilation, and signal reconstruction. The ambiguity isn't a bug in our evaluator. It's a structural property of any system where evaluation collapses distinctions that interpretation needs.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft17024gblbxtty4r5dr7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft17024gblbxtty4r5dr7.png" alt="Evaluation is many-to-one — multiple source expressions collapse to the same reduced form" width="720" height="440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This has a precise formal counterpart. In &lt;strong&gt;equality saturation&lt;/strong&gt;, an e-graph data structure maintains all equivalent representations of an expression simultaneously, exactly the way our dictionary check produces multiple valid readings at different abstraction levels. The &lt;code&gt;egg&lt;/code&gt; library (Willsey et al., POPL 2021) formalized this approach, and a key result is that the &lt;strong&gt;extraction problem&lt;/strong&gt; (i.e. choosing the "best" equivalent term from an e-graph according to a cost function) is NP-hard in general.&lt;/p&gt;

&lt;p&gt;That hardness result matters here. It tells us that even when we have a perfect representation of all valid interpretations (the e-graph), selecting the right one is fundamentally expensive. Simple cost functions (fewest nodes, shortest expression) give us a tractable approximation, but they don't capture intent. A learned cost model, trained on which interpretations users actually prefer, is a language model by another name. This is what it looks like when a formal system acquires a statistical component not as a convenience, but because the selection problem demands it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mathematicians Have the Same Problem
&lt;/h2&gt;

&lt;p&gt;This pattern shows up everywhere, including in how humans do mathematics.&lt;/p&gt;

&lt;p&gt;A mathematician works through a long calculation and arrives at some algebraic structure. Formally it's symbols on a page. But after years of training, they look at it and think: "of course! this is a Lie algebra." Or: "this has a Dirac delta function hiding in it."&lt;/p&gt;

&lt;p&gt;That recognition is &lt;strong&gt;not deduction&lt;/strong&gt;. They didn't derive it axiomatically in the moment. They pattern-matched against a vast internal library of structures, built up through years of supervised exposure: working problems, reading textbooks, being corrected by professors.&lt;/p&gt;

&lt;p&gt;And just like the combinator case, the recognition is ambiguous. The same structure might be a Lie algebra, a tangent space, or an instance of a fiber bundle. The mathematician picks the interpretation most useful in context, and that judgment comes from experience, not from the formalism.&lt;/p&gt;

&lt;p&gt;This is why mathematics takes years to learn. Part of that time goes to absorbing the sheer volume of formal content — modern mathematics is vast, and simply learning the definitions, theorems, and proof techniques is itself a major undertaking. But beyond that, what takes years is training the &lt;strong&gt;pattern recognition model&lt;/strong&gt; in the mathematician's head. Learning to look at a page of symbols and feel that something "smells like" a cohomology group or is "obviously" a convolution. Mathematicians use aesthetic and intuitive language because the recognition process operates partly below the level of conscious formal reasoning.&lt;/p&gt;

&lt;p&gt;The pedagogy even mirrors supervised learning: a student works through a problem; the professor says "do we see this is a Lie algebra?"; the student didn't see it; now they have a label; next time, they're more likely to recognize it. Over hundreds of examples with feedback, the model trains.&lt;/p&gt;

&lt;h2&gt;
  
  
  One Common Thread
&lt;/h2&gt;

&lt;p&gt;Combinator calculus, Mathematical practice, and Natural Language are three seemingly unrelated domains, but all exhibit the same structure:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Combinator Reflection&lt;/th&gt;
&lt;th&gt;Mathematics&lt;/th&gt;
&lt;th&gt;Natural Language&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;System&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Combinator calculus&lt;/td&gt;
&lt;td&gt;Symbolic algebra&lt;/td&gt;
&lt;td&gt;Grammar + vocabulary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Expression&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Reduced stack&lt;/td&gt;
&lt;td&gt;Calculation result&lt;/td&gt;
&lt;td&gt;Sentence&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Dictionary&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Word definitions&lt;/td&gt;
&lt;td&gt;Known structures&lt;/td&gt;
&lt;td&gt;Word meanings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ambiguity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Multiple valid readings&lt;/td&gt;
&lt;td&gt;Multiple recognizable patterns&lt;/td&gt;
&lt;td&gt;Multiple interpretations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Resolution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Heuristics or learned preference model&lt;/td&gt;
&lt;td&gt;Formal training + intuition&lt;/td&gt;
&lt;td&gt;Pragmatic inference + contextual knowledge&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;In every case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The formal content is &lt;strong&gt;produced&lt;/strong&gt; mechanically.&lt;/li&gt;
&lt;li&gt;Interpreting it requires &lt;strong&gt;recognition&lt;/strong&gt; that is not mechanical.&lt;/li&gt;
&lt;li&gt;Recognition involves &lt;strong&gt;ambiguity&lt;/strong&gt; that the formalism cannot resolve internally.&lt;/li&gt;
&lt;li&gt;Resolution requires &lt;strong&gt;judgment shaped by experience&lt;/strong&gt;, whether heuristic, learned, or pragmatic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The formalism produces. Recognition consumes. And they require fundamentally different mechanisms.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhihetccxl7habqp5cejo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhihetccxl7habqp5cejo.png" alt="The information gap — production is deterministic, interpretation is ambiguous" width="720" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Final remarks
&lt;/h2&gt;

&lt;p&gt;Any sufficiently expressive formal system that tries to understand its own outputs the way its users understand them will eventually benefit from something like a language model inside it. Not as an add-on or a convenience, but as a &lt;strong&gt;natural component&lt;/strong&gt;, because the gap between producing expressions and interpreting them is an information gap. Evaluation destroys distinctions that interpretation needs, and no amount of internal reasoning can recover what was never preserved.&lt;/p&gt;

&lt;p&gt;The gap is not a bug in the implementation. It's a structural property of non-injective evaluation. And the way we cope with it in programming, in mathematics, in everyday speech, is always the same: we learn to predict what was meant, because the formal system alone can't recover it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;p&gt;If these connections interest you, here are some threads worth pulling on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Concatenative Combinatory Logic
&lt;/h3&gt;

&lt;p&gt;Brent Kerby's &lt;em&gt;The Theory of Concatenative Combinators&lt;/em&gt; (2002) is the foundational text for understanding how combinator calculus works in a concatenative (stack-based) setting. It establishes the theoretical framework that languages like Joy and Factor build on, and explores complete combinator bases for the concatenative setting. Available at &lt;a href="http://tunes.org/~iepos/joy.html" rel="noopener noreferrer"&gt;tunes.org/~iepos/joy.html&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For a more recent and rigorous treatment, &lt;em&gt;Foundations of Dawn: The Untyped Concatenative Calculus&lt;/em&gt; (2021) by the Dawn language project formalizes the core calculus and explores how data types can be encoded purely from combinators. See &lt;a href="https://www.dawn-lang.org/posts/foundations-ucc/" rel="noopener noreferrer"&gt;dawn-lang.org&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  E-Graphs and Equality Saturation
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;egg&lt;/code&gt; library by Max Willsey et al. is the key reference for equality saturation. Their paper &lt;em&gt;egg: Fast and Extensible Equality Saturation&lt;/em&gt; (POPL 2021 Distinguished Paper) explains both the e-graph data structure and the extraction problem discussed in this post. See the &lt;a href="https://egraphs-good.github.io/" rel="noopener noreferrer"&gt;egg website&lt;/a&gt; and the &lt;a href="https://blog.sigplan.org/2021/04/06/equality-saturation-with-egg/" rel="noopener noreferrer"&gt;SIGPLAN blog post&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Self-Reference and Meaning
&lt;/h3&gt;

&lt;p&gt;Douglas Hofstadter's &lt;em&gt;Gödel, Escher, Bach: An Eternal Golden Braid&lt;/em&gt; (1979) remains the classic exploration of self-reference, formal systems, and the emergence of meaning. Relevant background for anyone interested in how systems that produce expressions relate to systems that interpret them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern Recognition in Mathematics
&lt;/h3&gt;

&lt;p&gt;Jacques Hadamard's &lt;em&gt;The Psychology of Invention in the Mathematical Field&lt;/em&gt; (1945) is a classic study of how mathematicians actually think, relying on visual imagery, aesthetic judgment, and sub-symbolic pattern recognition rather than purely formal deduction.&lt;/p&gt;

&lt;p&gt;George Pólya's &lt;em&gt;How to Solve It&lt;/em&gt; (1945) systematizes the heuristic, pattern-matching aspect of mathematical problem-solving that formal systems cannot capture.&lt;/p&gt;

&lt;p&gt;More recently, DeepMind's collaboration with mathematicians used ML to suggest conjectures in knot theory and representation theory that the mathematicians then proved (Davies et al., &lt;em&gt;Nature&lt;/em&gt;, 2021). A concrete instance of machines performing the same kind of pattern recognition that human mathematicians develop over years of training.&lt;/p&gt;

&lt;h3&gt;
  
  
  Language Models and Ambiguity
&lt;/h3&gt;

&lt;p&gt;For the linguistic side, the classic reference is &lt;em&gt;Speech and Language Processing&lt;/em&gt; by Dan Jurafsky and James Martin, which covers how statistical models handle ambiguity at every level of language. A free draft is available at &lt;a href="https://web.stanford.edu/~jurafsky/slp3/" rel="noopener noreferrer"&gt;web.stanford.edu/~jurafsky/slp3/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On how humans resolve ambiguity without statistical models, H.P. Grice's "Logic and Conversation" (1975) introduced the cooperative principle and conversational implicature — the framework linguists use to explain how listeners infer intent beyond literal meaning. Sperber and Wilson's &lt;em&gt;Relevance: Communication and Cognition&lt;/em&gt; (1986) extended this into a cognitive theory of pragmatic interpretation.&lt;/p&gt;




&lt;p&gt;What do you think about this topic? See you next time...&lt;/p&gt;

</description>
      <category>computerscience</category>
      <category>compiling</category>
      <category>programming</category>
      <category>logic</category>
    </item>
    <item>
      <title>How a Pushdown Automaton becomes a Parser [part 3]</title>
      <dc:creator>Jocer Franquiz</dc:creator>
      <pubDate>Sat, 28 Mar 2026 16:33:32 +0000</pubDate>
      <link>https://dev.to/jocerfranquiz/how-a-pushdown-automaton-becomes-a-parser-part-3-1ijd</link>
      <guid>https://dev.to/jocerfranquiz/how-a-pushdown-automaton-becomes-a-parser-part-3-1ijd</guid>
      <description>&lt;h2&gt;
  
  
  From Tokens to Trees: Four Paths to a Full Parser
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/jocerfranquiz/building-a-tokenizer-from-scratch-part-2-1o52"&gt;part 2&lt;/a&gt;, we built a &lt;a href="https://en.wikipedia.org/wiki/Pushdown_automaton" rel="noopener noreferrer"&gt;pushdown automaton&lt;/a&gt; &lt;a href="https://en.wikipedia.org/wiki/Finite-state_transducer" rel="noopener noreferrer"&gt;transducer&lt;/a&gt;, with 11 operations, 6 components, one stack. Our PDA Transducer turns nested &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; tags into a flat stream of tokens. In this post, we will explore what do we need to add to get a simple but practical parser that outputs a DOM tree.&lt;/p&gt;




&lt;h3&gt;
  
  
  Q: Is the transducer enough to parse HTML?
&lt;/h3&gt;

&lt;p&gt;No. The transducer takes &lt;code&gt;&amp;lt;div&amp;gt;&amp;lt;div&amp;gt;hi&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt; and emits:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[(OPEN, "div"), (OPEN, "div"), (TEXT, "hi"),
 (CLOSE, "div"), (CLOSE, "div")]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's a flat list. It tells you &lt;em&gt;what&lt;/em&gt; was in the input, but not &lt;em&gt;how things relate to each other&lt;/em&gt;. A parser needs to produce a &lt;a href="https://en.wikipedia.org/wiki/Parse_tree" rel="noopener noreferrer"&gt;tree&lt;/a&gt;, a structure where the outer &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; is the parent and the inner &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; is its child:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;div
 └── div
      └── "hi"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our transducer can't do this. Its only writable memory is the stack, and the stack is &lt;strong&gt;consumed during validation&lt;/strong&gt;. Every &lt;code&gt;PUSH&lt;/code&gt; is matched by a &lt;code&gt;POP&lt;/code&gt; to check nesting. By the time the transducer is done, the stack is empty. There's nowhere to &lt;em&gt;store&lt;/em&gt; the tree.&lt;/p&gt;

&lt;p&gt;To become a parser, the PDA needs a way to &lt;strong&gt;build and keep a tree structure in memory&lt;/strong&gt;. Not an easy task.&lt;/p&gt;




&lt;h3&gt;
  
  
  Q: There are multiple ways to give the PDA tree-building ability?
&lt;/h3&gt;

&lt;p&gt;Yes. Four classical computational models, each adding something different to the same PDA architecture we built in part 2.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;What you add&lt;/th&gt;
&lt;th&gt;Computation model&lt;/th&gt;
&lt;th&gt;Language family&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2-Stack machine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A second stack&lt;/td&gt;
&lt;td&gt;&lt;a href="https://en.wikipedia.org/wiki/Stack_machine" rel="noopener noreferrer"&gt;Stack machine&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Forth, PostScript, Factor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tree rewriting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A pool of cons cells + substitution rules&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://en.wikipedia.org/wiki/Lambda_calculus" rel="noopener noreferrer"&gt;Lambda calculus&lt;/a&gt; / rewriting&lt;/td&gt;
&lt;td&gt;Lisp, Scheme&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Stack combinators&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Combinator primitives (dup, swap, quote, apply)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://en.wikipedia.org/wiki/Combinatory_logic" rel="noopener noreferrer"&gt;Combinatory logic&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Joy, Cat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Register machine&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Writable random-access memory + ALU&lt;/td&gt;
&lt;td&gt;&lt;a href="https://en.wikipedia.org/wiki/Von_Neumann_architecture" rel="noopener noreferrer"&gt;Von Neumann&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Assembly, C&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All four give the PDA persistent, writable memory to store a tree. But each structures that memory differently. Each created a different programming tradition.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyx2slvhbf7secutqlz23.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyx2slvhbf7secutqlz23.png" alt="Four paths from PDA to full parser" width="800" height="462"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  The 2-Stack Machine (Forth, Factor)
&lt;/h3&gt;

&lt;p&gt;We add a second &lt;a href="https://en.wikipedia.org/wiki/Stack_(abstract_data_type)" rel="noopener noreferrer"&gt;LIFO&lt;/a&gt; stack. That's it. Two stacks cooperating through the finite control. This is the &lt;a href="https://en.wikipedia.org/wiki/Stack_machine" rel="noopener noreferrer"&gt;stack machine&lt;/a&gt;, the model behind &lt;a href="https://en.wikipedia.org/wiki/Forth_(programming_language)" rel="noopener noreferrer"&gt;Forth&lt;/a&gt; (Charles Moore, 1970), &lt;a href="https://en.wikipedia.org/wiki/PostScript" rel="noopener noreferrer"&gt;PostScript&lt;/a&gt;, and &lt;a href="https://en.wikipedia.org/wiki/Factor_(programming_language)" rel="noopener noreferrer"&gt;Factor&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack A&lt;/strong&gt; keeps its original job: validating nesting (&lt;code&gt;PUSH&lt;/code&gt; on open, &lt;code&gt;POP&lt;/code&gt; on close, &lt;code&gt;CMP&lt;/code&gt; to check the match). &lt;strong&gt;Stack B&lt;/strong&gt; is new. It accumulates completed subtrees. When a closing tag matches, the parser pops the validated tag from Stack A and pushes the finished node (with its children) onto Stack B.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parsing &lt;code&gt;&amp;lt;div&amp;gt;&amp;lt;div&amp;gt;hi&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;:&lt;/strong&gt; The transducer reads tokens as before. When it encounters &lt;code&gt;(CLOSE, "div")&lt;/code&gt; for the inner div, it pops &lt;code&gt;DIV&lt;/code&gt; from Stack A (validation) and pushes a completed node &lt;code&gt;div("hi")&lt;/code&gt; onto Stack B. When the outer &lt;code&gt;&amp;lt;/div&amp;gt;&lt;/code&gt; closes, it pops from Stack A again and assembles the final tree by popping the inner node from Stack B as its child. The result on Stack B: &lt;code&gt;div(div("hi"))&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7i0a3481fn2lx3tfwcr3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7i0a3481fn2lx3tfwcr3.png" alt="2-Stack PDA architecture" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two stacks can also simulate a &lt;a href="https://en.wikipedia.org/wiki/Turing_machine#The_machine" rel="noopener noreferrer"&gt;Turing machine tape&lt;/a&gt;: one stack holds everything left of the head, the other holds everything right. Move left = pop from left, push to right. Move right = the reverse. This is a well-known equivalence: a &lt;a href="https://en.wikipedia.org/wiki/Stack_machine#Two_stacks" rel="noopener noreferrer"&gt;2-stack PDA equals a Turing machine&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftgbhu22lacopkp830l5w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftgbhu22lacopkp830l5w.png" alt="Two stacks simulating a Turing machine tape" width="700" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Minimal addition, only 2 new operations&lt;/td&gt;
&lt;td&gt;Trees must be linearized into stack form&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Closest to the existing PDA architecture&lt;/td&gt;
&lt;td&gt;Deep trees require deep stacks, no random access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Simple to implement and reason about&lt;/td&gt;
&lt;td&gt;Awkward when you need to revisit earlier nodes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  Tree Rewriting (Lisp, Scheme)
&lt;/h3&gt;

&lt;p&gt;We replace the stack with a pool of dynamically allocated &lt;a href="https://en.wikipedia.org/wiki/Cons" rel="noopener noreferrer"&gt;cons&lt;/a&gt; cells. Each cell holds a pair of pointers: left (&lt;code&gt;CAR&lt;/code&gt;) and right (&lt;code&gt;CDR&lt;/code&gt;), forming a binary tree. Computation is &lt;a href="https://en.wikipedia.org/wiki/Pattern_matching" rel="noopener noreferrer"&gt;pattern matching&lt;/a&gt; on tree structure and substitution: find a subtree that matches a rule, replace it with the result. This is the &lt;a href="https://en.wikipedia.org/wiki/Lambda_calculus" rel="noopener noreferrer"&gt;lambda calculus&lt;/a&gt; model: the foundation of &lt;a href="https://en.wikipedia.org/wiki/Lisp_(programming_language)" rel="noopener noreferrer"&gt;Lisp&lt;/a&gt; (&lt;a href="https://en.wikipedia.org/wiki/Alonzo_Church" rel="noopener noreferrer"&gt;Alonzo Church&lt;/a&gt;, 1936; &lt;a href="https://en.wikipedia.org/wiki/John_McCarthy_(computer_scientist)" rel="noopener noreferrer"&gt;John McCarthy&lt;/a&gt;, 1958), &lt;a href="https://en.wikipedia.org/wiki/Scheme_(programming_language)" rel="noopener noreferrer"&gt;Scheme&lt;/a&gt;, and &lt;a href="https://en.wikipedia.org/wiki/Racket_(programming_language)" rel="noopener noreferrer"&gt;Racket&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The finite control drives parsing as before, but instead of pushing symbols onto a stack, it allocates cons cells in memory. Each opening tag creates a new node. Each closing tag triggers a CONS that links children to their parent. The tree is built directly in memory, with no linearization needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you add to the PDA:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;New operations&lt;/th&gt;
&lt;th&gt;New data structures&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;CONS&lt;/code&gt;, &lt;code&gt;CAR&lt;/code&gt;, &lt;code&gt;CDR&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Pool of dynamically allocated &lt;code&gt;cons&lt;/code&gt; cells&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Parsing &lt;code&gt;&amp;lt;div&amp;gt;&amp;lt;div&amp;gt;hi&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;:&lt;/strong&gt; When the parser sees &lt;code&gt;(TEXT, "hi")&lt;/code&gt;, it allocates a leaf cell holding &lt;code&gt;"hi"&lt;/code&gt;. When &lt;code&gt;(CLOSE, "div")&lt;/code&gt; arrives for the inner div, a CONS links &lt;code&gt;"hi"&lt;/code&gt; as the child of a new &lt;code&gt;div&lt;/code&gt; node. When the outer &lt;code&gt;&amp;lt;/div&amp;gt;&lt;/code&gt; closes, another CONS links the inner &lt;code&gt;div&lt;/code&gt; node as the child of the outer &lt;code&gt;div&lt;/code&gt; node. The result is a tree in memory: &lt;code&gt;(div . (div . "hi"))&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7s2bq0xfx4a5jxfpxvrm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7s2bq0xfx4a5jxfpxvrm.png" alt="Tree rewriting architecture" width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Trees are the native data structure. No encoding needed&lt;/td&gt;
&lt;td&gt;Requires &lt;a href="https://en.wikipedia.org/wiki/Garbage_collection_(computer_science)" rel="noopener noreferrer"&gt;garbage collection&lt;/a&gt; or manual memory management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Natural representation for &lt;a href="https://en.wikipedia.org/wiki/S-expression" rel="noopener noreferrer"&gt;S-expressions&lt;/a&gt;: code and data share the same form&lt;/td&gt;
&lt;td&gt;More complex memory model than a stack&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pattern matching + substitution is powerful for tree transformations&lt;/td&gt;
&lt;td&gt;Pointer chasing is slow on modern hardware (poor &lt;a href="https://en.wikipedia.org/wiki/Locality_of_reference" rel="noopener noreferrer"&gt;cache locality&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  Stack Combinators (Joy, Cat)
&lt;/h3&gt;

&lt;p&gt;We keep one stack. Don't add memory. Instead, change what the stack is allowed to hold. In our PDA, the stack holds passive symbols (like &lt;code&gt;DIV&lt;/code&gt;). In a &lt;a href="https://en.wikipedia.org/wiki/Combinatory_logic" rel="noopener noreferrer"&gt;combinator&lt;/a&gt; machine, the stack holds &lt;em&gt;executable programs&lt;/em&gt;. &lt;code&gt;quote&lt;/code&gt; takes an operation and pushes it as data. &lt;code&gt;apply&lt;/code&gt; pops it and executes it. The stack becomes simultaneously data storage and code. This is the foundation of &lt;a href="https://en.wikipedia.org/wiki/Concatenative_programming_language" rel="noopener noreferrer"&gt;concatenative languages&lt;/a&gt;: &lt;a href="https://en.wikipedia.org/wiki/Joy_(programming_language)" rel="noopener noreferrer"&gt;Joy&lt;/a&gt; (&lt;a href="https://en.wikipedia.org/wiki/Joy_(programming_language)" rel="noopener noreferrer"&gt;Manfred von Thun&lt;/a&gt;, 1990s), &lt;a href="https://en.wikipedia.org/wiki/Cat_(programming_language)" rel="noopener noreferrer"&gt;Cat&lt;/a&gt;, and &lt;a href="https://en.wikipedia.org/wiki/Factor_(programming_language)" rel="noopener noreferrer"&gt;Factor&lt;/a&gt;. The theoretical roots go back to &lt;a href="https://en.wikipedia.org/wiki/Moses_Sch%C3%B6nfinkel" rel="noopener noreferrer"&gt;Moses Schonfinkel&lt;/a&gt; (1920s) and &lt;a href="https://en.wikipedia.org/wiki/Haskell_Curry" rel="noopener noreferrer"&gt;Haskell Curry&lt;/a&gt; (1930s).&lt;/p&gt;

&lt;p&gt;The parser quotes partial tree fragments and pushes them onto the stack as data. When a closing tag arrives, combinators like &lt;code&gt;swap&lt;/code&gt; and &lt;code&gt;dup&lt;/code&gt; rearrange the fragments, and &lt;code&gt;apply&lt;/code&gt; assembles them into a larger tree. No heap, no RAM, no second stack. The stack itself is the only memory, and its entries can be either data or programs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you add to the PDA:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;New operations&lt;/th&gt;
&lt;th&gt;New data structures&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;DUP&lt;/code&gt;, &lt;code&gt;SWAP&lt;/code&gt;, &lt;code&gt;QUOTE&lt;/code&gt;, &lt;code&gt;APPLY&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;None actually. Stack entries just become richer (data + code)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Parsing &lt;code&gt;&amp;lt;div&amp;gt;&amp;lt;div&amp;gt;hi&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;:&lt;/strong&gt; When the parser sees &lt;code&gt;(TEXT, "hi")&lt;/code&gt;, it quotes the text as a data fragment and pushes it. When &lt;code&gt;(CLOSE, "div")&lt;/code&gt; arrives, it quotes a &lt;code&gt;make-div-node&lt;/code&gt; operation, swaps it under the &lt;code&gt;"hi"&lt;/code&gt; fragment, and applies, producing a quoted &lt;code&gt;div("hi")&lt;/code&gt; node on the stack. The outer &lt;code&gt;&amp;lt;/div&amp;gt;&lt;/code&gt; repeats the pattern, composing the inner node into the outer one. The result: a single quoted tree &lt;code&gt;[div(div("hi"))]&lt;/code&gt; on top of the stack.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxx07lgv24054iaporhn0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxx07lgv24054iaporhn0.png" alt="Stack combinators architecture" width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No new memory means zero hardware cost&lt;/td&gt;
&lt;td&gt;Hardest model to reason about for most developers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Elegant &lt;a href="https://en.wikipedia.org/wiki/Tacit_programming" rel="noopener noreferrer"&gt;point-free&lt;/a&gt; composition. No variables, no names&lt;/td&gt;
&lt;td&gt;Stack shuffling (&lt;code&gt;swap&lt;/code&gt;, &lt;code&gt;dup&lt;/code&gt;, &lt;code&gt;rot&lt;/code&gt;) gets complex fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code and data unification is powerful for metaprogramming&lt;/td&gt;
&lt;td&gt;Small community, fewer learning resources&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  The Register Machine (Assembly, C)
&lt;/h3&gt;

&lt;p&gt;Weplace the stack with addressable RAM memory. Add an &lt;a href="https://en.wikipedia.org/wiki/Arithmetic_logic_unit" rel="noopener noreferrer"&gt;ALU&lt;/a&gt; with arithmetic operations. The finite control gains index registers for addressing. This is the &lt;a href="https://en.wikipedia.org/wiki/Von_Neumann_architecture" rel="noopener noreferrer"&gt;Von Neumann architecture&lt;/a&gt;, the model &lt;a href="https://en.wikipedia.org/wiki/John_von_Neumann" rel="noopener noreferrer"&gt;John von Neumann&lt;/a&gt; described in 1945, building on &lt;a href="https://en.wikipedia.org/wiki/Alan_Turing" rel="noopener noreferrer"&gt;Alan Turing&lt;/a&gt;'s theoretical work (1936). Every CPU since x86, ARM, RISC-V, has this as it's essentially this model.&lt;/p&gt;

&lt;p&gt;Random access changes everything. The parser allocates tree nodes at arbitrary memory addresses and links them by pointer. Need to revisit a node three levels up? Just follow the address — no popping through a stack. The tree lives in RAM as a set of linked records, each holding a tag name, a pointer to its first child, and a pointer to its next sibling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What you add to the PDA:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;New operations&lt;/th&gt;
&lt;th&gt;New data structures&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;LOAD&lt;/code&gt;, &lt;code&gt;STORE&lt;/code&gt;, &lt;code&gt;ADD&lt;/code&gt;, &lt;code&gt;SUB&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;RAM + ALU + index registers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Parsing &lt;code&gt;&amp;lt;div&amp;gt;&amp;lt;div&amp;gt;hi&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;:&lt;/strong&gt; The parser allocates a record at address 0x00 for the outer &lt;code&gt;div&lt;/code&gt;. When &lt;code&gt;(OPEN, "div")&lt;/code&gt; arrives for the inner div, it allocates at 0x01 and writes &lt;code&gt;0x00&lt;/code&gt; as its parent pointer. When &lt;code&gt;(TEXT, "hi")&lt;/code&gt; arrives, it allocates at 0x02 and writes &lt;code&gt;0x01&lt;/code&gt; as its parent. The closing tags update child pointers. The result is a linked structure in RAM:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Address&lt;/th&gt;
&lt;th&gt;Tag&lt;/th&gt;
&lt;th&gt;First child&lt;/th&gt;
&lt;th&gt;Next sibling&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0x00&lt;/td&gt;
&lt;td&gt;div&lt;/td&gt;
&lt;td&gt;→ 0x01&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x01&lt;/td&gt;
&lt;td&gt;div&lt;/td&gt;
&lt;td&gt;→ 0x02&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x02&lt;/td&gt;
&lt;td&gt;"hi"&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;td&gt;null&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0oy0m2ea1rb324zq7o9j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0oy0m2ea1rb324zq7o9j.png" alt="Register machine architecture" width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://en.wikipedia.org/wiki/Random_access" rel="noopener noreferrer"&gt;Random access&lt;/a&gt;: read/write any node by address, O(1)&lt;/td&gt;
&lt;td&gt;Most complex addition: RAM, ALU, index registers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maps directly to real hardware (silicon is random-access)&lt;/td&gt;
&lt;td&gt;Requires manual memory management or allocator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Every programming language compiles to this&lt;/td&gt;
&lt;td&gt;Pointer arithmetic is a source of bugs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is the path that won. Not because it's the most elegant, in fact the 2-stack machine is simpler, the tree rewriter is more natural for trees, the combinator machine needs no extra memory at all. It won because &lt;strong&gt;transistors are random-access by design&lt;/strong&gt;. RAM is cheap to build in silicon. The other three models require the hardware to simulate structured access patterns on top of flat memory. An extra layer the register machine skips entirely.&lt;/p&gt;




&lt;h3&gt;
  
  
  A side effect: Turing completeness
&lt;/h3&gt;

&lt;p&gt;Each of these four additions was designed to solve a specific problem: giving the PDA enough writable memory to build a parse tree. But each one also has a deeper consequence: it makes the machine &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/Turing_completeness" rel="noopener noreferrer"&gt;Turing complete&lt;/a&gt;&lt;/strong&gt;, a machine capable of computing anything that any computer can compute.&lt;/p&gt;

&lt;p&gt;This isn't a coincidence. To build an arbitrary tree, you need to store and retrieve an arbitrary amount of structured data. That's exactly what separates a &lt;a href="https://en.wikipedia.org/wiki/Context-free_language" rel="noopener noreferrer"&gt;context-free&lt;/a&gt; machine (our PDA) from a &lt;a href="https://en.wikipedia.org/wiki/Turing_machine" rel="noopener noreferrer"&gt;Turing machine&lt;/a&gt;. The four paths don't just add tree-building, they each provide enough general-purpose memory to simulate a Turing machine tape.&lt;/p&gt;

&lt;p&gt;Here's how they compare side by side:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;2-Stack&lt;/th&gt;
&lt;th&gt;Tree Rewriting&lt;/th&gt;
&lt;th&gt;Stack Combinators&lt;/th&gt;
&lt;th&gt;Register Machine&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;New ops&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;PUSH_B, POP_B&lt;/td&gt;
&lt;td&gt;CONS, CAR, CDR&lt;/td&gt;
&lt;td&gt;DUP, SWAP, QUOTE, APPLY&lt;/td&gt;
&lt;td&gt;LOAD, STORE, ADD, SUB, MUL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;New op count&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total ops (11 + N)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;New data structure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Second LIFO stack&lt;/td&gt;
&lt;td&gt;Pool of cons cells&lt;/td&gt;
&lt;td&gt;None (enriched stack)&lt;/td&gt;
&lt;td&gt;RAM + ALU + index registers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Primary data structure&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Two LIFO stacks&lt;/td&gt;
&lt;td&gt;Binary tree (cons cells)&lt;/td&gt;
&lt;td&gt;One stack (data + code)&lt;/td&gt;
&lt;td&gt;Flat array of addressed cells&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;How composition works&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stack effects (before/after)&lt;/td&gt;
&lt;td&gt;S-expressions nested lists&lt;/td&gt;
&lt;td&gt;
&lt;a href="https://en.wikipedia.org/wiki/Tacit_programming" rel="noopener noreferrer"&gt;Point-free&lt;/a&gt; sequencing&lt;/td&gt;
&lt;td&gt;Instruction sequences&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Why it's Turing complete&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Two stacks simulate a tape&lt;/td&gt;
&lt;td&gt;Tree substitution simulates a tape&lt;/td&gt;
&lt;td&gt;quote + apply = self-modifying programs&lt;/td&gt;
&lt;td&gt;RAM = direct tape simulation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd668r1no9niv2m2f3nmm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd668r1no9niv2m2f3nmm.png" alt="Hierarchy from combinational logic to Turing completeness" width="700" height="540"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We started at the bottom of this hierarchy in &lt;a href="https://dev.to/jocerfranquiz/building-a-tokenizer-from-scratch-od5"&gt;part 1&lt;/a&gt; with a finite state machine. In &lt;a href="https://dev.to/jocerfranquiz/building-a-tokenizer-from-scratch-part-2-1o52"&gt;part 2&lt;/a&gt; we added a stack and crossed into context-free territory. Now we've seen the four doors at the top; four ways to cross into Turing completeness. All equivalent in power, all different in structure.&lt;/p&gt;




&lt;h3&gt;
  
  
  Q: If the PDA is so rigid, can it work with something flexible like an LLM?
&lt;/h3&gt;

&lt;p&gt;Yes, and this is where things get practical.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;From PDA to transformer, step by step:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;PDA&lt;/strong&gt;: hand-coded rules, explicit stack, one language (HTML)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learned PDA&lt;/strong&gt;: same architecture, learn transition rules from data (&lt;a href="https://arxiv.org/abs/1506.02516" rel="noopener noreferrer"&gt;Grefenstette et al., 2015&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RNN&lt;/strong&gt;: replace explicit stack with hidden state vector; approximates a stack but struggles with deep nesting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LSTM&lt;/strong&gt; — add gates to hidden state; better stack approximation; pre-transformer breakthrough&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transformer&lt;/strong&gt; — replace sequential hidden state with &lt;a href="https://en.wikipedia.org/wiki/Attention_(machine_learning)" rel="noopener noreferrer"&gt;attention&lt;/a&gt; over entire input; random access to every previous token, not just stack top&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each step trades &lt;strong&gt;transparency for generality&lt;/strong&gt;. The PDA handles one grammar perfectly and you can see why. The transformer handles every grammar approximately and you can't see how.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Critical limit:&lt;/strong&gt; The PDA has unbounded stack, arbitrarily deep nesting, always. The transformer has finite context and finite precision — it's really a &lt;strong&gt;bounded-depth PDA&lt;/strong&gt;. It can approximate stack behavior within its context window, but it can't guarantee correct nesting beyond that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Research backing:&lt;/strong&gt; Attention heads in transformers learn to simulate stack operations. Specific heads match opening tags to closing tags by attending back to the most recent unmatched opener — the attention pattern literally looks like LIFO behavior (&lt;a href="https://arxiv.org/abs/2305.18827" rel="noopener noreferrer"&gt;Murty et al.&lt;/a&gt;, &lt;a href="https://arxiv.org/abs/2310.16620" rel="noopener noreferrer"&gt;Merrill et al.&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The practical combination: grammar-constrained decoding.&lt;/strong&gt; Run the PDA alongside the LLM. The LLM generates tokens based on meaning. The PDA vetoes structurally invalid ones.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Step 1: LLM emits "&amp;lt;ul&amp;gt;"       stack: [UL]         ✅
Step 2: LLM emits "&amp;lt;li&amp;gt;"       stack: [UL, LI]     ✅
Step 3: LLM emits "Apple"      stack: [UL, LI]     ✅
Step 4: LLM wants "&amp;lt;/ul&amp;gt;"      stack: [UL, LI]     ❌ BLOCKED
        top is LI, not UL
        next best: "&amp;lt;/li&amp;gt;"     stack: [UL]         ✅
Step 5: LLM emits "&amp;lt;li&amp;gt;"       stack: [UL, LI]     ✅
Step 6: LLM emits "Banana"                         ✅
Step 7: LLM emits "&amp;lt;/li&amp;gt;"      stack: [UL]         ✅
Step 8: LLM emits "&amp;lt;/ul&amp;gt;"      stack: []           ✅
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Step 4 is the key: the LLM tried to close &lt;code&gt;&amp;lt;/ul&amp;gt;&lt;/code&gt; while &lt;code&gt;&amp;lt;li&amp;gt;&lt;/code&gt; was still open. The PDA masked it out, and the LLM's second-best token &lt;code&gt;&amp;lt;/li&amp;gt;&lt;/code&gt; took over.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;LLM&lt;/th&gt;
&lt;th&gt;PDA&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Decides&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"Apple" and "Banana" are fruits&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;&amp;lt;/ul&amp;gt;&lt;/code&gt; can't close before &lt;code&gt;&amp;lt;/li&amp;gt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Good at&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Meaning, creativity, world knowledge&lt;/td&gt;
&lt;td&gt;Structure, nesting, grammar rules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fails at&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Guaranteeing valid nesting&lt;/td&gt;
&lt;td&gt;Knowing what a fruit is&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0hn38kadxgg4thtqtvep.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0hn38kadxgg4thtqtvep.png" alt="Grammar-constrained decoding — LLM + PDA collaboration" width="800" height="583"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This combination is already in production:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/dottxt-ai/outlines" rel="noopener noreferrer"&gt;Outlines&lt;/a&gt;&lt;/strong&gt; (Python): attaches a grammar to any HuggingFace model&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/ggerganov/llama.cpp/blob/master/grammars/README.md" rel="noopener noreferrer"&gt;llama.cpp GBNF&lt;/a&gt;&lt;/strong&gt;: grammar-constrained decoding in C++&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/guidance-ai/guidance" rel="noopener noreferrer"&gt;Guidance&lt;/a&gt;&lt;/strong&gt; (Microsoft): interleaves free generation with grammar constraints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://platform.openai.com/docs/guides/structured-outputs" rel="noopener noreferrer"&gt;OpenAI structured outputs&lt;/a&gt;&lt;/strong&gt;: grammar enforcement server-side via JSON schema&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The PDA we built in parts 1 and 2 is the &lt;em&gt;exact&lt;/em&gt; mechanism these tools use to guarantee structure. A hand-coded automaton means the simplest kind of program, guarding the output of the most complex kind.&lt;/p&gt;




&lt;h3&gt;
  
  
  What's next
&lt;/h3&gt;

&lt;p&gt;In parts 1 and 2 we built a tokenizer. In this post we saw four ways to extend it into a full parser, and how the simplest of those machines can guard the output of the most powerful ones.&lt;/p&gt;

&lt;p&gt;Next up: we pick the register machine path and build a parser that writes a tree to memory.&lt;/p&gt;

&lt;p&gt;Stay tuned...&lt;/p&gt;

</description>
      <category>parsing</category>
      <category>computerscience</category>
      <category>learning</category>
      <category>programming</category>
    </item>
    <item>
      <title>Building a Tokenizer from Scratch [part 2]</title>
      <dc:creator>Jocer Franquiz</dc:creator>
      <pubDate>Sat, 28 Mar 2026 16:30:18 +0000</pubDate>
      <link>https://dev.to/jocerfranquiz/building-a-tokenizer-from-scratch-part-2-1p8h</link>
      <guid>https://dev.to/jocerfranquiz/building-a-tokenizer-from-scratch-part-2-1p8h</guid>
      <description>&lt;h2&gt;
  
  
  From FSM to PDA
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/jocerfranquiz/building-a-tokenizer-from-scratch-od5"&gt;part 1&lt;/a&gt;, we built a working FSM that recognizes &lt;code&gt;&amp;lt;div&amp;gt;text&amp;lt;/div&amp;gt;&lt;/code&gt; using just 7 primitives mapped 1:1 to assembly opcodes. But FSMs have a hard limit: they can't handle nested structures like &lt;code&gt;&amp;lt;div&amp;gt;&amp;lt;div&amp;gt;hello&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;. In this post, we climb the &lt;a href="https://en.wikipedia.org/wiki/Chomsky_hierarchy" rel="noopener noreferrer"&gt;Chomsky hierarchy&lt;/a&gt; from &lt;a href="https://en.wikipedia.org/wiki/Finite-state_machine" rel="noopener noreferrer"&gt;finite state machines&lt;/a&gt; to &lt;a href="https://en.wikipedia.org/wiki/Pushdown_automaton" rel="noopener noreferrer"&gt;pushdown automata&lt;/a&gt;, build a PDA that recognizes nested &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; tags, and then turn it into a transducer that emits tokens. In other words we are building the core of a &lt;a href="https://en.wikipedia.org/wiki/Lexical_analysis" rel="noopener noreferrer"&gt;lexer&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Q: Why can't FSMs handle nested structures?
&lt;/h3&gt;

&lt;p&gt;Because an FSM has a &lt;strong&gt;fixed number of states&lt;/strong&gt;, and that's all the memory it has.&lt;/p&gt;

&lt;p&gt;Consider nested divs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div&amp;gt;&amp;lt;div&amp;gt;&amp;lt;div&amp;gt;hello&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To correctly match closing tags, you need to &lt;strong&gt;count&lt;/strong&gt; how many &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;s you've opened so you know how many &lt;code&gt;&amp;lt;/div&amp;gt;&lt;/code&gt;s to expect. An FSM with, say, 12 states can handle nesting up to some fixed depth — but someone can always write HTML nested one level deeper than your states can track.&lt;/p&gt;

&lt;p&gt;Put simply:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;1 level deep&lt;/strong&gt; → you need states to remember "I'm 1 div in"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2 levels deep&lt;/strong&gt; → different states for "I'm 2 divs in"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;n levels deep&lt;/strong&gt; → you'd need n distinct state configurations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since n is unbounded but your state count is finite, you &lt;strong&gt;run out of states&lt;/strong&gt;. There's no way to encode "how deep am I?" in a fixed-size state register when the depth is arbitrary.&lt;/p&gt;

&lt;p&gt;This is exactly what the &lt;strong&gt;stack&lt;/strong&gt; in a pushdown automaton solves. Every time you see &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, you push onto the stack; every &lt;code&gt;&amp;lt;/div&amp;gt;&lt;/code&gt;, you pop. The stack grows as needed, giving you &lt;strong&gt;unbounded memory&lt;/strong&gt; in one direction (depth), which is precisely what nesting requires.&lt;/p&gt;




&lt;h3&gt;
  
  
  Q: So, FSMs needs memory to "remember" where they are in the nested structure, correct?
&lt;/h3&gt;

&lt;p&gt;It depends on what you mean by "memory."&lt;/p&gt;

&lt;p&gt;An FSM &lt;strong&gt;does&lt;/strong&gt; have memory, but it's extremely limited. The &lt;strong&gt;current state itself&lt;/strong&gt; is the memory. If your FSM is in state &lt;code&gt;S7&lt;/code&gt;, that encodes &lt;em&gt;something&lt;/em&gt; about what it has seen so far. That's how it knows it's partway through reading &lt;code&gt;&amp;lt;/div&amp;gt;&lt;/code&gt; vs still in content.&lt;/p&gt;

&lt;p&gt;But that memory is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fixed-size&lt;/strong&gt;, bounded by the number of states, decided at design time&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lossy&lt;/strong&gt;, it can't remember the full history of what it read, only a compressed summary encoded in which state it's in&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Non-growable&lt;/strong&gt;, it can't allocate more memory as input gets longer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz3a9h7q6a5d2ltjk37ho.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fz3a9h7q6a5d2ltjk37ho.png" alt="FSM Architecture" width="600" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So when people say "FSMs have no memory," they really mean &lt;strong&gt;no auxiliary memory&lt;/strong&gt;, that means no stack, no tape, no buffer that grows with input. The only thing an FSM "remembers" is which state it's currently in.&lt;/p&gt;

&lt;p&gt;A PDA adds exactly one auxiliary memory structure: &lt;strong&gt;a stack&lt;/strong&gt;. That single addition is enough to go from "can't handle nesting" to "can handle nesting."&lt;/p&gt;




&lt;h3&gt;
  
  
  Q: What is the &lt;a href="https://en.wikipedia.org/wiki/Stack_(abstract_data_type)" rel="noopener noreferrer"&gt;stack&lt;/a&gt;, formally?
&lt;/h3&gt;

&lt;p&gt;A stack is a &lt;strong&gt;&lt;a href="https://en.wikipedia.org/wiki/LIFO_(computing)" rel="noopener noreferrer"&gt;last-in, first-out (LIFO)&lt;/a&gt;&lt;/strong&gt; data structure with two operations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PUSH(symbol)&lt;/strong&gt;: place a symbol on top&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POP&lt;/strong&gt;: remove and return the top symbol&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So if your stack is &lt;code&gt;ABB&lt;/code&gt;, a PUSH(C) gives &lt;code&gt;ABBC&lt;/code&gt;, and a POP returns &lt;code&gt;C&lt;/code&gt; leaving &lt;code&gt;ABB&lt;/code&gt; again.&lt;/p&gt;

&lt;p&gt;The key properties:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;You can only access the top&lt;/strong&gt;: no reading the middle or bottom&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unbounded size&lt;/strong&gt;: it can grow as large as needed (unlike the FSM's fixed states)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It starts empty&lt;/strong&gt;: at the beginning of computation, the stack contains nothing (or a special bottom marker like &lt;code&gt;$&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. It's the simplest possible auxiliar memory that's still useful. You get only sequential access from one end. But that restriction is exactly what matches the structure of nesting: the most recently opened thing is the next thing that needs to close.&lt;/p&gt;




&lt;h3&gt;
  
  
  Q: Which operations and structures do we need to add to the seven primitives to create a PDA that handles nested structures?
&lt;/h3&gt;

&lt;p&gt;We need &lt;strong&gt;two new operations&lt;/strong&gt; and &lt;strong&gt;one new structure&lt;/strong&gt;:&lt;/p&gt;

&lt;h4&gt;
  
  
  New structure
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;STACK&lt;/strong&gt;: a LIFO storage addressed by a stack pointer (SP)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Updated primitives table
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Assembly equivalent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;READ&lt;/td&gt;
&lt;td&gt;Load current input symbol&lt;/td&gt;
&lt;td&gt;FSM&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MOV reg, [src]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;CMP&lt;/td&gt;
&lt;td&gt;Test value equality&lt;/td&gt;
&lt;td&gt;FSM&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CMP reg, imm&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;BRANCH&lt;/td&gt;
&lt;td&gt;Conditional jump&lt;/td&gt;
&lt;td&gt;FSM&lt;/td&gt;
&lt;td&gt;&lt;code&gt;JE / JNE label&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;JMP&lt;/td&gt;
&lt;td&gt;Unconditional jump&lt;/td&gt;
&lt;td&gt;FSM&lt;/td&gt;
&lt;td&gt;&lt;code&gt;JMP label&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;ASSIGN&lt;/td&gt;
&lt;td&gt;Update state register&lt;/td&gt;
&lt;td&gt;FSM&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MOV reg, imm&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;ADVANCE&lt;/td&gt;
&lt;td&gt;Move input pointer forward&lt;/td&gt;
&lt;td&gt;FSM&lt;/td&gt;
&lt;td&gt;&lt;code&gt;INC reg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;HALT&lt;/td&gt;
&lt;td&gt;Terminate execution&lt;/td&gt;
&lt;td&gt;FSM&lt;/td&gt;
&lt;td&gt;&lt;code&gt;HLT&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;PUSH&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Place symbol on top of stack&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;PDA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PUSH reg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;POP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Remove and return top of stack&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;PDA&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POP reg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two operations and one structure turn an FSM into a PDA.&lt;/p&gt;

&lt;p&gt;x86 has &lt;strong&gt;native PUSH/POP instructions&lt;/strong&gt; with a hardware stack pointer (&lt;code&gt;SP/ESP/RSP&lt;/code&gt;), so the 1:1 mapping to CPU instructions still holds. PDAs are just as mechanically efficient as FSMs. The only cost is that memory usage now grows with nesting depth instead of being fixed.&lt;/p&gt;

&lt;p&gt;You could also add a &lt;strong&gt;PEEK&lt;/strong&gt; operation (read top without removing), but it's not strictly necessary — you can always POP then PUSH back. It's a convenience, not a primitive.&lt;/p&gt;




&lt;h3&gt;
  
  
  Q: What's the simplest PDA architecture?
&lt;/h3&gt;

&lt;p&gt;A PDA architecture needs exactly &lt;strong&gt;four components&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Input tape&lt;/strong&gt; — the string being read (read-only, left-to-right)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Input pointer&lt;/strong&gt; — current position on the tape&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State register&lt;/strong&gt; — holds the current state from a finite set Q&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stack&lt;/strong&gt; — LIFO storage over alphabet Γ&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And the &lt;strong&gt;nine operations&lt;/strong&gt; from our table are the instruction set that operates on these four components.&lt;/p&gt;

&lt;p&gt;Compared to the FSM architecture from &lt;a href="https://dev.to/jocerfranquiz/building-a-tokenizer-from-scratch-od5"&gt;part 1&lt;/a&gt;, we added exactly &lt;strong&gt;one component&lt;/strong&gt; (the stack) and &lt;strong&gt;two operations&lt;/strong&gt; (PUSH, POP). The stack pointer (SP) lives inside the finite control alongside the state register — it's a register, not part of the stack itself.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw01vxq5cakgb8kad9idt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw01vxq5cakgb8kad9idt.png" alt="PDA Architecture" width="800" height="546"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The PDA architecture is a &lt;a href="https://en.wikipedia.org/wiki/Von_Neumann_architecture" rel="noopener noreferrer"&gt;von Neumann architecture&lt;/a&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PDA component&lt;/th&gt;
&lt;th&gt;Von Neumann equivalent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Input tape&lt;/td&gt;
&lt;td&gt;Memory (data segment)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Input pointer&lt;/td&gt;
&lt;td&gt;Program counter / index register&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State register&lt;/td&gt;
&lt;td&gt;CPU register (accumulator)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stack pointer&lt;/td&gt;
&lt;td&gt;SP register&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stack (in RAM)&lt;/td&gt;
&lt;td&gt;Stack segment of memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9 operations&lt;/td&gt;
&lt;td&gt;Subset of the instruction set&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What's missing for full Von Neumann: writable memory, random access, arithmetic, and stored program. The automata hierarchy maps directly onto hardware capability:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FSM&lt;/strong&gt; → combinational logic + a register&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PDA&lt;/strong&gt; → CPU + stack segment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Turing machine&lt;/strong&gt; → CPU + full random-access memory = von Neumann&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Building a PDA for nested &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt; recognition
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Pseudocode
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight armasm"&gt;&lt;code&gt;&lt;span class="nl"&gt;STATES&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;S0&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;TAG_OPEN&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;TAG_NAME&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;CONTENT&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;CLOSE_SLASH&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;CLOSE_NAME&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ACCEPT&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;DEAD&lt;/span&gt;

&lt;span class="nl"&gt;STACK&lt;/span&gt; &lt;span class="nb"&gt;ALPHABET&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="err"&gt;Γ&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;DIV&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nl"&gt;START&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;PUSH&lt;/span&gt;&lt;span class="err"&gt;($)&lt;/span&gt;                    &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;bottom&lt;/span&gt; &lt;span class="nv"&gt;marker&lt;/span&gt;
  &lt;span class="nb"&gt;ASSIGN&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;S0&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;S0&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;READ&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;CMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'&amp;lt;'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;BRANCH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;eq&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;TAG_OPEN&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;JMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;DEAD&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;TAG_OPEN&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;
  &lt;span class="nb"&gt;READ&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;CMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'/'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;BRANCH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;eq&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;CLOSE_SLASH&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="sc"&gt;'s a closing tag&lt;/span&gt;
  &lt;span class="nb"&gt;CMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'d'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;BRANCH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;eq&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;TAG_NAME&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;      &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;it&lt;/span&gt;&lt;span class="sc"&gt;'s an opening tag&lt;/span&gt;
  &lt;span class="nb"&gt;JMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;DEAD&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;TAG_NAME&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;                     &lt;span class="err"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;read&lt;/span&gt; &lt;span class="s2"&gt;"div&amp;gt;"&lt;/span&gt;
  &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;
  &lt;span class="nb"&gt;READ&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="c"&gt;; CMP(ch, 'i'); BRANCH(ne → DEAD)&lt;/span&gt;
  &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;
  &lt;span class="nb"&gt;READ&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="c"&gt;; CMP(ch, 'v'); BRANCH(ne → DEAD)&lt;/span&gt;
  &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;
  &lt;span class="nb"&gt;READ&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="c"&gt;; CMP(ch, '&amp;gt;'); BRANCH(ne → DEAD)&lt;/span&gt;
  &lt;span class="nb"&gt;PUSH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;DIV&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;                   &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="nv"&gt;matched&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;,&lt;/span&gt; &lt;span class="nv"&gt;push&lt;/span&gt; &lt;span class="nv"&gt;it&lt;/span&gt;
  &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;
  &lt;span class="nb"&gt;ASSIGN&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;CONTENT&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;CONTENT&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;READ&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;CMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'&amp;lt;'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;BRANCH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;eq&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;TAG_OPEN&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;      &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="nv"&gt;could&lt;/span&gt; &lt;span class="nv"&gt;be&lt;/span&gt; &lt;span class="nv"&gt;nested&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;or&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nv"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nb"&gt;CMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;EOF&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;BRANCH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;eq&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;DEAD&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;           &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;input&lt;/span&gt; &lt;span class="nv"&gt;ended&lt;/span&gt; &lt;span class="nv"&gt;with&lt;/span&gt; &lt;span class="nv"&gt;open&lt;/span&gt; &lt;span class="nv"&gt;tags&lt;/span&gt;
  &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;                     &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;consume&lt;/span&gt; &lt;span class="nv"&gt;text&lt;/span&gt; &lt;span class="nv"&gt;character&lt;/span&gt;
  &lt;span class="nb"&gt;JMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;CONTENT&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;CLOSE_SLASH&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;                  &lt;span class="err"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;inside&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;/&lt;/span&gt;
  &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;
  &lt;span class="nb"&gt;READ&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="c"&gt;; CMP(ch, 'd'); BRANCH(ne → DEAD)&lt;/span&gt;
  &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;
  &lt;span class="nb"&gt;READ&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="c"&gt;; CMP(ch, 'i'); BRANCH(ne → DEAD)&lt;/span&gt;
  &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;
  &lt;span class="nb"&gt;READ&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="c"&gt;; CMP(ch, 'v'); BRANCH(ne → DEAD)&lt;/span&gt;
  &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;
  &lt;span class="nb"&gt;READ&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="c"&gt;; CMP(ch, '&amp;gt;'); BRANCH(ne → DEAD)&lt;/span&gt;
  &lt;span class="nb"&gt;POP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;top&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;                    &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="nv"&gt;pop&lt;/span&gt; &lt;span class="nv"&gt;the&lt;/span&gt; &lt;span class="nv"&gt;stack&lt;/span&gt;
  &lt;span class="nb"&gt;CMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;top&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;DIV&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;BRANCH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ne&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;DEAD&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;           &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;mismatch&lt;/span&gt;
  &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;
  &lt;span class="nb"&gt;READ&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;CMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;EOF&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;BRANCH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;eq&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;CHECK_EMPTY&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;ASSIGN&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;CONTENT&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;             &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;more&lt;/span&gt; &lt;span class="nv"&gt;input&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;keep&lt;/span&gt; &lt;span class="nv"&gt;going&lt;/span&gt;

&lt;span class="nl"&gt;CHECK_EMPTY&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;POP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;top&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;CMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;top&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;BRANCH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;eq&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;ACCEPT&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;         &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;stack&lt;/span&gt; &lt;span class="nv"&gt;empty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;all&lt;/span&gt; &lt;span class="nv"&gt;tags&lt;/span&gt; &lt;span class="nv"&gt;matched&lt;/span&gt;
  &lt;span class="nb"&gt;JMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;DEAD&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;                   &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;leftover&lt;/span&gt; &lt;span class="nv"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;unbalanced&lt;/span&gt;

&lt;span class="nl"&gt;ACCEPT&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;HALT&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;accept&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;DEAD&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;HALT&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;reject&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The entire FSM logic is reused. The only additions are three PUSH/POP calls. One PUSH per &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;, one POP per &lt;code&gt;&amp;lt;/div&amp;gt;&lt;/code&gt;, one final POP to verify the stack is empty.&lt;/p&gt;

&lt;h4&gt;
  
  
  Trace: &lt;code&gt;&amp;lt;div&amp;gt;&amp;lt;div&amp;gt;hi&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input: &amp;lt; d i v &amp;gt; &amp;lt; d i v &amp;gt; h i &amp;lt; / d i v &amp;gt; &amp;lt; / d i v &amp;gt; EOF
       0 1 2 3 4 5 6 7 8 9 ...

Stack starts: [ $ ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;1. First &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;State     | Read | Action              | Stack
----------|------|----------------------|--------
S0        | &amp;lt;    | match, → TAG_OPEN   | [ $ ]
TAG_OPEN  | d    | not '/', → TAG_NAME | [ $ ]
TAG_NAME  | i    | match               | [ $ ]
          | v    | match               | [ $ ]
          | &amp;gt;    | match, PUSH(DIV)    | [ $ DIV ]
          |      | → CONTENT           |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Second &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;State     | Read | Action              | Stack
----------|------|----------------------|-----------
CONTENT   | &amp;lt;    | → TAG_OPEN          | [ $ DIV ]
TAG_OPEN  | d    | not '/', → TAG_NAME | [ $ DIV ]
TAG_NAME  | i    | match               | [ $ DIV ]
          | v    | match               | [ $ DIV ]
          | &amp;gt;    | match, PUSH(DIV)    | [ $ DIV DIV ]
          |      | → CONTENT           |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Text "hi"&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;State     | Read | Action              | Stack
----------|------|----------------------|-------------
CONTENT   | h    | not '&amp;lt;', ADVANCE    | [ $ DIV DIV ]
CONTENT   | i    | not '&amp;lt;', ADVANCE    | [ $ DIV DIV ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. First &lt;code&gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;State       | Read | Action            | Stack
------------|------|--------------------|--------------
CONTENT     | &amp;lt;    | → TAG_OPEN        | [ $ DIV DIV ]
TAG_OPEN    | /    | → CLOSE_SLASH     | [ $ DIV DIV ]
CLOSE_SLASH | d    | match             | [ $ DIV DIV ]
            | i    | match             | [ $ DIV DIV ]
            | v    | match             | [ $ DIV DIV ]
            | &amp;gt;    | match, POP → DIV  | [ $ DIV ]
            |      | DIV == DIV ✓      |
            |      | read next         |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Second &lt;code&gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;State       | Read | Action            | Stack
------------|------|--------------------|---------
CONTENT     | &amp;lt;    | → TAG_OPEN        | [ $ DIV ]
TAG_OPEN    | /    | → CLOSE_SLASH     | [ $ DIV ]
CLOSE_SLASH | d    | match             | [ $ DIV ]
            | i    | match             | [ $ DIV ]
            | v    | match             | [ $ DIV ]
            | &amp;gt;    | match, POP → DIV  | [ $ ]
            |      | DIV == DIV ✓      |
            |      | read next         |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. End of input&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;State       | Read | Action            | Stack
------------|------|--------------------|---------
            | EOF  | → CHECK_EMPTY     | [ $ ]
CHECK_EMPTY |      | POP → $           | [ ]
            |      | $ == $ ✓          |
            |      | → ACCEPT          |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result: ACCEPT&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The stack grew to depth 2 (two DIVs), then shrank back to just &lt;code&gt;$&lt;/code&gt;, confirming every open tag had a matching close. An FSM would have needed dedicated states for each depth level. This PDA handles any depth with the same 8 states.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcco78r95r78h3x1ko02c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcco78r95r78h3x1ko02c.png" alt="PDA State diagram" width="800" height="546"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Q: Our PDA can parse nested structures and output accept or reject. But a real tokenizer needs to emit tokens, right?
&lt;/h3&gt;

&lt;p&gt;Right. The PDA is still just a &lt;strong&gt;recognizer&lt;/strong&gt;. To become a tokenizer, we need to understand what changes formally.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;recognizer&lt;/strong&gt; takes an input string and produces a &lt;strong&gt;single binary answer&lt;/strong&gt;: accept or reject.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input: a string over alphabet&lt;/li&gt;
&lt;li&gt;Output: &lt;strong&gt;yes&lt;/strong&gt; or &lt;strong&gt;no&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A &lt;a href="https://en.wikipedia.org/wiki/Finite-state_transducer" rel="noopener noreferrer"&gt;transducer&lt;/a&gt; takes an input string and produces an &lt;strong&gt;output string&lt;/strong&gt; (or sequence of output symbols). It transforms input into output.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input: a string over alphabet&lt;/li&gt;
&lt;li&gt;Output: a string y over an &lt;strong&gt;output alphabet Δ&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A &lt;strong&gt;tokenizer&lt;/strong&gt; is a transducer: it reads characters (Σ = ASCII) and emits tokens (Δ = the set of possible tokens).&lt;/p&gt;

&lt;h3&gt;
  
  
  Q: What is ?
&lt;/h3&gt;

&lt;p&gt;An &lt;strong&gt;output buffer&lt;/strong&gt; is a tape, simpler than the stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Append-only&lt;/strong&gt;: you can only write to the end, never read back or modify&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write-only&lt;/strong&gt;: the machine never consults its own output to make decisions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unbounded&lt;/strong&gt;: it grows as tokens are emitted&lt;/li&gt;
&lt;li&gt;Starts empty&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Output structure
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;OUTPUT ALPHABET:  Δ = TokenType × Σ*
TOKEN TYPES:      TokenType = { OPEN_TAG, CLOSE_TAG, TEXT }
OUTPUT BUFFER:    append-only list of (type, value) pairs, starts empty
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For &lt;code&gt;&amp;lt;div&amp;gt;&amp;lt;div&amp;gt;hi&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt;, the output buffer should end up as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
  (OPEN_TAG,  "div"),
  (OPEN_TAG,  "div"),
  (TEXT,      "hi"),
  (CLOSE_TAG, "div"),
  (CLOSE_TAG, "div")
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Updated operations table (11 operations)
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Assembly&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;READ&lt;/td&gt;
&lt;td&gt;Load current input symbol&lt;/td&gt;
&lt;td&gt;FSM&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MOV reg, [src]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;CMP&lt;/td&gt;
&lt;td&gt;Test value equality&lt;/td&gt;
&lt;td&gt;FSM&lt;/td&gt;
&lt;td&gt;&lt;code&gt;CMP reg, reg/imm&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;BRANCH&lt;/td&gt;
&lt;td&gt;Conditional jump&lt;/td&gt;
&lt;td&gt;FSM&lt;/td&gt;
&lt;td&gt;&lt;code&gt;JE / JNE label&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;JMP&lt;/td&gt;
&lt;td&gt;Unconditional jump&lt;/td&gt;
&lt;td&gt;FSM&lt;/td&gt;
&lt;td&gt;&lt;code&gt;JMP label&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;ASSIGN&lt;/td&gt;
&lt;td&gt;Update state register&lt;/td&gt;
&lt;td&gt;FSM&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MOV reg, imm&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;ADVANCE&lt;/td&gt;
&lt;td&gt;Move input pointer forward&lt;/td&gt;
&lt;td&gt;FSM&lt;/td&gt;
&lt;td&gt;&lt;code&gt;INC reg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;HALT&lt;/td&gt;
&lt;td&gt;Terminate execution&lt;/td&gt;
&lt;td&gt;FSM&lt;/td&gt;
&lt;td&gt;&lt;code&gt;HLT&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;PUSH&lt;/td&gt;
&lt;td&gt;Place symbol on top of stack&lt;/td&gt;
&lt;td&gt;PDA&lt;/td&gt;
&lt;td&gt;&lt;code&gt;PUSH reg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;POP&lt;/td&gt;
&lt;td&gt;Remove and return top of stack&lt;/td&gt;
&lt;td&gt;PDA&lt;/td&gt;
&lt;td&gt;&lt;code&gt;POP reg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;MARK&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Save current input position&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Transducer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MOV reg, reg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;EMIT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Append token to output buffer&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Transducer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;MOV [dst], reg&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h4&gt;
  
  
  Transducer Architecture: 6 components
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Input tape (read-only)&lt;/li&gt;
&lt;li&gt;Input pointer&lt;/li&gt;
&lt;li&gt;State register&lt;/li&gt;
&lt;li&gt;Stack pointer + Stack&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mark register&lt;/strong&gt; — saves start position of current token&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Output buffer&lt;/strong&gt; — append-only, unbounded&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb3decimwtp4z1p7a0hll.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb3decimwtp4z1p7a0hll.png" alt="Transducer Architecture" width="800" height="558"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h4&gt;
  
  
  Pseudocode (transducer)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight armasm"&gt;&lt;code&gt;&lt;span class="nl"&gt;STATES&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;S0&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;TAG_OPEN&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;TAG_NAME&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;CONTENT&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;CONTENT_LOOP&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;CONTENT_DONE&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;CLOSE_SLASH&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;CHECK_EMPTY&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ACCEPT&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;DEAD&lt;/span&gt;

&lt;span class="nl"&gt;INPUT&lt;/span&gt; &lt;span class="nb"&gt;ALPHABET&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;  &lt;span class="err"&gt;Σ&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;ASCII&lt;/span&gt;
&lt;span class="nl"&gt;STACK&lt;/span&gt; &lt;span class="nb"&gt;ALPHABET&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;  &lt;span class="err"&gt;Γ&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;DIV&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="nl"&gt;OUTPUT&lt;/span&gt; &lt;span class="nb"&gt;ALPHABET&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="err"&gt;Δ&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;OPEN_TAG&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;CLOSE_TAG&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;TEXT&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt; &lt;span class="err"&gt;×&lt;/span&gt; &lt;span class="err"&gt;Σ&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="nl"&gt;START&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;PUSH&lt;/span&gt;&lt;span class="err"&gt;($)&lt;/span&gt;
  &lt;span class="nb"&gt;ASSIGN&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;S0&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;S0&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;READ&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;CMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'&amp;lt;'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;BRANCH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;eq&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;TAG_OPEN&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;JMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;DEAD&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;TAG_OPEN&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;
  &lt;span class="nb"&gt;READ&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;CMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'/'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;BRANCH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;eq&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;CLOSE_SLASH&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;CMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'d'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;BRANCH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;eq&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;TAG_NAME&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;JMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;DEAD&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;TAG_NAME&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;
  &lt;span class="nb"&gt;READ&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="c"&gt;; CMP(ch, 'i'); BRANCH(ne → DEAD)&lt;/span&gt;
  &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;
  &lt;span class="nb"&gt;READ&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="c"&gt;; CMP(ch, 'v'); BRANCH(ne → DEAD)&lt;/span&gt;
  &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;
  &lt;span class="nb"&gt;READ&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="c"&gt;; CMP(ch, '&amp;gt;'); BRANCH(ne → DEAD)&lt;/span&gt;
  &lt;span class="nb"&gt;PUSH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;DIV&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;EMIT&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;OPEN_TAG&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"div"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;           &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="nv"&gt;emit&lt;/span&gt; &lt;span class="nv"&gt;opening&lt;/span&gt; &lt;span class="nv"&gt;tag&lt;/span&gt; &lt;span class="nv"&gt;token&lt;/span&gt;
  &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;
  &lt;span class="nb"&gt;ASSIGN&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;CONTENT&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;CONTENT&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;MARK&lt;/span&gt;                             &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="nv"&gt;save&lt;/span&gt; &lt;span class="nv"&gt;start&lt;/span&gt; &lt;span class="nv"&gt;of&lt;/span&gt; &lt;span class="nv"&gt;text&lt;/span&gt; &lt;span class="nv"&gt;region&lt;/span&gt;
&lt;span class="nl"&gt;CONTENT_LOOP&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;READ&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;CMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'&amp;lt;'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;BRANCH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;eq&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;CONTENT_DONE&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;CMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;EOF&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;BRANCH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;eq&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;DEAD&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;
  &lt;span class="nb"&gt;JMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;CONTENT_LOOP&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;CONTENT_DONE&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;CMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;mark&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;                   &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;any&lt;/span&gt; &lt;span class="nv"&gt;text&lt;/span&gt; &lt;span class="nv"&gt;between&lt;/span&gt; &lt;span class="nv"&gt;tags&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
  &lt;span class="nb"&gt;BRANCH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;eq&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;TAG_OPEN&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;            &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;no&lt;/span&gt; &lt;span class="nv"&gt;text&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;skip&lt;/span&gt; &lt;span class="nv"&gt;emit&lt;/span&gt;
  &lt;span class="nb"&gt;EMIT&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;input&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;mark&lt;/span&gt;&lt;span class="mf"&gt;..&lt;/span&gt;&lt;span class="nv"&gt;pos&lt;/span&gt;&lt;span class="o"&gt;])&lt;/span&gt;     &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="nv"&gt;emit&lt;/span&gt; &lt;span class="nv"&gt;text&lt;/span&gt; &lt;span class="nv"&gt;token&lt;/span&gt;
  &lt;span class="nb"&gt;JMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;TAG_OPEN&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;CLOSE_SLASH&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;
  &lt;span class="nb"&gt;READ&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="c"&gt;; CMP(ch, 'd'); BRANCH(ne → DEAD)&lt;/span&gt;
  &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;
  &lt;span class="nb"&gt;READ&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="c"&gt;; CMP(ch, 'i'); BRANCH(ne → DEAD)&lt;/span&gt;
  &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;
  &lt;span class="nb"&gt;READ&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="c"&gt;; CMP(ch, 'v'); BRANCH(ne → DEAD)&lt;/span&gt;
  &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;
  &lt;span class="nb"&gt;READ&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="c"&gt;; CMP(ch, '&amp;gt;'); BRANCH(ne → DEAD)&lt;/span&gt;
  &lt;span class="nb"&gt;POP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;top&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;CMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;top&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;DIV&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;BRANCH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ne&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;DEAD&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;EMIT&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;CLOSE_TAG&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"div"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;          &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="err"&gt;←&lt;/span&gt; &lt;span class="nv"&gt;emit&lt;/span&gt; &lt;span class="nv"&gt;closing&lt;/span&gt; &lt;span class="nv"&gt;tag&lt;/span&gt; &lt;span class="nv"&gt;token&lt;/span&gt;
  &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;
  &lt;span class="nb"&gt;READ&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;CMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ch&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;EOF&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;BRANCH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;eq&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;CHECK_EMPTY&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;ASSIGN&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;CONTENT&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;CHECK_EMPTY&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;POP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;top&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;CMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;top&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;BRANCH&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;eq&lt;/span&gt; &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nv"&gt;ACCEPT&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="nb"&gt;JMP&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;DEAD&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;

&lt;span class="nl"&gt;ACCEPT&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;HALT&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;accept&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;                     &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="nv"&gt;output&lt;/span&gt; &lt;span class="nv"&gt;buffer&lt;/span&gt; &lt;span class="nv"&gt;contains&lt;/span&gt; &lt;span class="nv"&gt;all&lt;/span&gt; &lt;span class="nv"&gt;tokens&lt;/span&gt;

&lt;span class="nl"&gt;DEAD&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
  &lt;span class="nb"&gt;HALT&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;reject&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Trace: &lt;code&gt;&amp;lt;div&amp;gt;&amp;lt;div&amp;gt;hi&amp;lt;/div&amp;gt;&amp;lt;/div&amp;gt;&lt;/code&gt; (transducer)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Input: &amp;lt; d i v &amp;gt; &amp;lt; d i v &amp;gt; h i &amp;lt; / d i v &amp;gt; &amp;lt; / d i v &amp;gt; EOF
Pos:   0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23

Stack starts:  [ $ ]
Output starts: [ ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;1. First &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;State       | Pos | Read | Action              | Stack     | Output
------------|-----|------|----------------------|-----------|-------
S0          |  0  | &amp;lt;    | match, → TAG_OPEN   | [ $ ]     |
TAG_OPEN    |  1  | d    | not '/', → TAG_NAME | [ $ ]     |
TAG_NAME    |  2  | i    | match               | [ $ ]     |
            |  3  | v    | match               | [ $ ]     |
            |  4  | &amp;gt;    | match               | [ $ ]     |
            |     |      | PUSH(DIV)           | [ $ DIV ] |
            |     |      | EMIT(OPEN_TAG,"div")|           | ← (OPEN_TAG, "div")
            |  5  |      | ADVANCE, → CONTENT  |           |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Enter CONTENT, see &lt;code&gt;&amp;lt;&lt;/code&gt; immediately&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;State        | Pos | Read | Action             | Stack     | Output
-------------|-----|------|--------------------|-----------|-------
CONTENT      |  5  |      | MARK → mark=5      | [ $ DIV ] |
CONTENT_LOOP |  5  | &amp;lt;    | CMP(mark,pos): 5=5 |           |
             |     |      | equal, skip emit   |           |
             |     |      | → TAG_OPEN         |           |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Second &lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;State       | Pos | Read | Action              | Stack         | Output
------------|-----|------|----------------------|---------------|-------
TAG_OPEN    |  6  | d    | not '/', → TAG_NAME | [ $ DIV ]     |
TAG_NAME    |  7  | i    | match               | [ $ DIV ]     |
            |  8  | v    | match               | [ $ DIV ]     |
            |  9  | &amp;gt;    | match               | [ $ DIV ]     |
            |     |      | PUSH(DIV)           | [ $ DIV DIV ] |
            |     |      | EMIT(OPEN_TAG,"div")|               | ← (OPEN_TAG, "div")
            | 10  |      | ADVANCE, → CONTENT  |               |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Text "hi"&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;State        | Pos | Read | Action             | Stack         | Output
-------------|-----|------|--------------------|---------------|-------
CONTENT      | 10  |      | MARK → mark=10     | [ $ DIV DIV ] |
CONTENT_LOOP | 10  | h    | not '&amp;lt;', ADVANCE   |               |
CONTENT_LOOP | 11  | i    | not '&amp;lt;', ADVANCE   |               |
CONTENT_LOOP | 12  | &amp;lt;    | CMP(mark,pos):10≠12|              |
             |     |      | EMIT(TEXT, "hi")   |               | ← (TEXT, "hi")
             |     |      | → TAG_OPEN         |               |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. First &lt;code&gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;State       | Pos | Read | Action              | Stack         | Output
------------|-----|------|----------------------|---------------|-------
TAG_OPEN    | 13  | /    | → CLOSE_SLASH       | [ $ DIV DIV ] |
CLOSE_SLASH | 14  | d    | match               |               |
            | 15  | i    | match               |               |
            | 16  | v    | match               |               |
            | 17  | &amp;gt;    | match               |               |
            |     |      | POP → DIV           | [ $ DIV ]     |
            |     |      | DIV == DIV ✓        |               |
            |     |      | EMIT(CLOSE_TAG,"div")|              | ← (CLOSE_TAG, "div")
            | 18  | &amp;lt;    | ADVANCE, not EOF    |               |
            |     |      | → CONTENT           |               |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. Enter CONTENT, see &lt;code&gt;&amp;lt;&lt;/code&gt; immediately&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;State        | Pos | Read | Action              | Stack     | Output
-------------|-----|------|----------------------|-----------|-------
CONTENT      | 18  |      | MARK → mark=18      | [ $ DIV ] |
CONTENT_LOOP | 18  | &amp;lt;    | CMP(mark,pos): 18=18|           |
             |     |      | equal, skip emit    |           |
             |     |      | → TAG_OPEN          |           |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;7. Second &lt;code&gt;&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;State       | Pos | Read | Action               | Stack     | Output
------------|-----|------|------------------------|-----------|-------
TAG_OPEN    | 19  | /    | → CLOSE_SLASH          | [ $ DIV ] |
CLOSE_SLASH | 20  | d    | match                  |           |
            | 21  | i    | match                  |           |
            | 22  | v    | match                  |           |
            | 23  | &amp;gt;    | match                  |           |
            |     |      | POP → DIV              | [ $ ]     |
            |     |      | DIV == DIV ✓           |           |
            |     |      | EMIT(CLOSE_TAG, "div") |           | ← (CLOSE_TAG, "div")
            | 24  | EOF  | ADVANCE, EOF           |           |
            |     |      | → CHECK_EMPTY          |           |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;8. End of input&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;State       | Pos | Read | Action              | Stack | Output
------------|-----|------|----------------------|-------|-------
CHECK_EMPTY |     |      | POP → $              | [ ]   |
            |     |      | $ == $ ✓             |       |
            |     |      | → ACCEPT             |       |
ACCEPT      |     |      | HALT(accept)         |       |
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Final output buffer
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
  (OPEN_TAG,  "div"),
  (OPEN_TAG,  "div"),
  (TEXT,      "hi"),
  (CLOSE_TAG, "div"),
  (CLOSE_TAG, "div")
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same 10 states, same stack operations. The three EMITs fired at exactly the right boundaries, and the two &lt;code&gt;CMP(mark, pos)&lt;/code&gt; guards correctly skipped empty text regions between consecutive tags.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvd6miytn8d201har8bv0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvd6miytn8d201har8bv0.png" alt="State diagram for PDA transducer tokenizing nested div tags" width="800" height="585"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Q: Is the tokenizer a lexer?
&lt;/h3&gt;

&lt;p&gt;Yes. Tokenizer, lexer, lexical analyzer, scanner, all the same thing. Characters in, tokens out.&lt;/p&gt;

&lt;p&gt;The distinction that matters is &lt;strong&gt;lexer vs parser&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lexer&lt;/strong&gt; (what we built): characters in, tokens out. Flat stream. Answers: "what are the words?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parser&lt;/strong&gt;: tokens in, tree out. Hierarchical structure. Answers: "how do the words relate to each other?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our transducer is a lexer. It outputs a flat list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(OPEN_TAG, "div"), (OPEN_TAG, "div"), (TEXT, "hi"), (CLOSE_TAG, "div"), (CLOSE_TAG, "div")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A parser would produce a tree:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;div
 └─ div
     └─ "hi"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Our PDA already &lt;em&gt;knows&lt;/em&gt; the tree structure in some way, the stack depth at any point tells us exactly where we are in the hierarchy. We just don't capture it. We throw that information away and emit a flat stream. Building the tree means using the stack to capture that structure as we go.&lt;/p&gt;




&lt;h3&gt;
  
  
  What's next
&lt;/h3&gt;

&lt;p&gt;We started this post with an FSM that couldn't count, and ended with a transducer that emits tokens from arbitrarily nested HTML. Along the way, a pattern kept surfacing: climbing the Chomsky hierarchy (FSM → PDA → Turing machine) mirrors the historical evolution of CPU architecture (register → stack segment → full random-access memory). That parallel is not a coincidence, and we will keep pulling on that thread.&lt;/p&gt;

&lt;p&gt;Next up: &lt;strong&gt;a lexer becomes a parser, a Turing Complete machine&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you have questions or want to dig deeper into any of these topics, drop a comment below. Follow to catch Part 3 when it lands. Stay tuned...&lt;/p&gt;

</description>
      <category>parsing</category>
      <category>computerscience</category>
      <category>learning</category>
      <category>programming</category>
    </item>
    <item>
      <title>Finite State Machine from first principles [part 1]</title>
      <dc:creator>Jocer Franquiz</dc:creator>
      <pubDate>Tue, 24 Mar 2026 22:12:17 +0000</pubDate>
      <link>https://dev.to/jocerfranquiz/building-a-tokenizer-from-scratch-od5</link>
      <guid>https://dev.to/jocerfranquiz/building-a-tokenizer-from-scratch-od5</guid>
      <description>&lt;h2&gt;
  
  
  From Combinational Logic to FSM: Q/A
&lt;/h2&gt;

&lt;p&gt;The starter point is Combinational Logic. The idea is to understand what do we really need to add to CL to create a Finite State Machine.&lt;/p&gt;

&lt;p&gt;At the end, we ended up building a working FSM that can recognize a complete &lt;code&gt;&amp;lt;div&amp;gt;text&amp;lt;/div&amp;gt;&lt;/code&gt; structure, using just 7 primitives. Funny enough, those primitives are equivalent to 7 Assembly opcodes (not a coincidence, this was all planned by the Elders).&lt;/p&gt;




&lt;h3&gt;
  
  
  Q: Automata theory has a class hierarchy, starting from combinational logic. Right?
&lt;/h3&gt;

&lt;p&gt;Yes, that's a clean way to frame it. The hierarchy goes from "no memory" to "more memory":&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Combinational logic&lt;/strong&gt; — output depends only on current input. No memory, no state. Just pure boolean functions (&lt;code&gt;AND&lt;/code&gt;, &lt;code&gt;OR&lt;/code&gt;, etc.).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Finite State Machine (FSM)&lt;/strong&gt; — adds memory in the form of &lt;em&gt;state&lt;/em&gt;. Output depends on current input &lt;strong&gt;+&lt;/strong&gt; current state. Memory is finite and fixed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pushdown Automaton (PDA)&lt;/strong&gt; — FSM + a &lt;strong&gt;stack&lt;/strong&gt;. The stack gives it unlimited but structured memory (LIFO). This is what recognizes nested structures like matching parentheses.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Turing Machine&lt;/strong&gt; — FSM + an &lt;strong&gt;infinite tape&lt;/strong&gt; (read/write). The most powerful model; can simulate any computation.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each level can recognize a strictly larger class of languages (the Chomsky hierarchy):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Machine&lt;/th&gt;
&lt;th&gt;Language class&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Combinational&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Boolean expression&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FSM&lt;/td&gt;
&lt;td&gt;Regular&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/ab*c/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PDA&lt;/td&gt;
&lt;td&gt;Context-free&lt;/td&gt;
&lt;td&gt;Balanced &lt;code&gt;{}[]()&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Turing&lt;/td&gt;
&lt;td&gt;Recursively enumerable&lt;/td&gt;
&lt;td&gt;Anything computable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For parsing HTML, you need at minimum a &lt;strong&gt;PDA&lt;/strong&gt; (because of nested tags), but in practice browsers use a &lt;em&gt;stateful tokenizer&lt;/em&gt; (FSM) + a &lt;em&gt;tree builder&lt;/em&gt; (stack-based) — which together behave like a PDA.&lt;/p&gt;




&lt;h3&gt;
  
  
  Q: Which combinational logic operators do we need to implement a finite state machine? Conceptually.
&lt;/h3&gt;

&lt;p&gt;Conceptually, you only need three:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AND&lt;/strong&gt; — "state is X &lt;em&gt;and&lt;/em&gt; input is Y"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OR&lt;/strong&gt; — "condition A &lt;em&gt;or&lt;/em&gt; condition B leads to the same result"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NOT&lt;/strong&gt; — negate a condition&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These three form a &lt;strong&gt;functionally complete set&lt;/strong&gt; — you can express any boolean function with just AND, OR, NOT.&lt;/p&gt;

&lt;p&gt;The core of an FSM is its &lt;strong&gt;transition function&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;next_state = f(current_state, input)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That function is purely combinational — it's just a truth table. Every row is a conjunction (AND) of conditions, and you OR the rows together to select the next state. Something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if state == A AND input == 'x' → go to B
if state == A AND input == 'y' → go to C
if state == B AND input == 'x' → go to A
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each line is AND. Multiple lines that share an outcome are OR'd together.&lt;/p&gt;

&lt;p&gt;NOT comes in when you need to express "any input that is &lt;em&gt;not&lt;/em&gt; this character."&lt;/p&gt;

&lt;p&gt;That's the combinational part. The &lt;strong&gt;state register&lt;/strong&gt; (what actually holds the current state between steps) is the sequential part — it's where memory enters the picture.&lt;/p&gt;




&lt;h3&gt;
  
  
  Q: What else do we need from a computational point of view? Something like &lt;code&gt;inc&lt;/code&gt;, &lt;code&gt;if/else&lt;/code&gt;, &lt;code&gt;cmp&lt;/code&gt;, etc.
&lt;/h3&gt;

&lt;p&gt;Here is the complete set of primitive operations for an FSM:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Why an FSM needs it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;READ&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Get the current input symbol&lt;/td&gt;
&lt;td&gt;The machine must &lt;em&gt;consume&lt;/em&gt; input&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CMP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Test equality of two values&lt;/td&gt;
&lt;td&gt;Check state and input against known constants&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BRANCH&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Conditional jump&lt;/td&gt;
&lt;td&gt;Select which transition fires&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JMP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Unconditional jump&lt;/td&gt;
&lt;td&gt;Loop back to process next character&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ASSIGN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Write a value to a register&lt;/td&gt;
&lt;td&gt;Update the state register&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ADVANCE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Move input pointer forward&lt;/td&gt;
&lt;td&gt;Step through the input string&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HALT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stop execution&lt;/td&gt;
&lt;td&gt;Accept or reject; terminate the machine&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Seven operations total. No arithmetic, no stack, no heap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt; an FSM that accepts strings matching &lt;code&gt;ab*c&lt;/code&gt; (an &lt;code&gt;a&lt;/code&gt;, then zero or more &lt;code&gt;b&lt;/code&gt;s, then &lt;code&gt;c&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;States: &lt;code&gt;S0&lt;/code&gt; (start), &lt;code&gt;S1&lt;/code&gt; (got &lt;code&gt;a&lt;/code&gt;), &lt;code&gt;ACCEPT&lt;/code&gt;, &lt;code&gt;DEAD&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight armasm"&gt;&lt;code&gt;&lt;span class="nl"&gt;LOOP&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;READ&lt;/span&gt;   &lt;span class="nv"&gt;char&lt;/span&gt;                &lt;span class="c"&gt;; get current input symbol&lt;/span&gt;

    &lt;span class="c"&gt;; ── S0: expecting 'a' ──&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S0&lt;/span&gt;
    &lt;span class="nb"&gt;BRANCH&lt;/span&gt; &lt;span class="nv"&gt;not_s0&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'a'&lt;/span&gt;
    &lt;span class="nb"&gt;BRANCH&lt;/span&gt; &lt;span class="nv"&gt;s0_fail&lt;/span&gt;
    &lt;span class="nb"&gt;ASSIGN&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S1&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;next&lt;/span&gt;

  &lt;span class="nf"&gt;s0_fail&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;ASSIGN&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;DEAD&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;next&lt;/span&gt;

  &lt;span class="nf"&gt;not_s0&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;; ── S1: expecting 'b' or 'c' ──&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S1&lt;/span&gt;
    &lt;span class="nb"&gt;BRANCH&lt;/span&gt; &lt;span class="nv"&gt;not_s1&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'b'&lt;/span&gt;
    &lt;span class="nb"&gt;BRANCH&lt;/span&gt; &lt;span class="nv"&gt;s1_not_b&lt;/span&gt;
    &lt;span class="nb"&gt;ASSIGN&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S1&lt;/span&gt;           &lt;span class="c"&gt;; stay in S1&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;next&lt;/span&gt;

  &lt;span class="nf"&gt;s1_not_b&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'c'&lt;/span&gt;
    &lt;span class="nb"&gt;BRANCH&lt;/span&gt; &lt;span class="nv"&gt;s1_fail&lt;/span&gt;
    &lt;span class="nb"&gt;ASSIGN&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ACCEPT&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;next&lt;/span&gt;

  &lt;span class="nf"&gt;s1_fail&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;ASSIGN&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;DEAD&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;next&lt;/span&gt;

  &lt;span class="nf"&gt;not_s1&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;; ── DEAD or ACCEPT + more input → DEAD ──&lt;/span&gt;
    &lt;span class="nb"&gt;ASSIGN&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;DEAD&lt;/span&gt;

  &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;                    &lt;span class="c"&gt;; move to next character&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;input_end&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;true&lt;/span&gt;
    &lt;span class="nb"&gt;BRANCH&lt;/span&gt; &lt;span class="nv"&gt;LOOP&lt;/span&gt;                &lt;span class="c"&gt;; not at end → loop&lt;/span&gt;

    &lt;span class="c"&gt;; ── input exhausted ──&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ACCEPT&lt;/span&gt;
    &lt;span class="nb"&gt;BRANCH&lt;/span&gt; &lt;span class="nv"&gt;reject&lt;/span&gt;
    &lt;span class="nb"&gt;HALT&lt;/span&gt;   &lt;span class="nv"&gt;accept&lt;/span&gt;

  &lt;span class="nb"&gt;reject&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;HALT&lt;/span&gt;   &lt;span class="nv"&gt;reject&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Trace with input &lt;code&gt;"abbc"&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;char&lt;/th&gt;
&lt;th&gt;state before&lt;/th&gt;
&lt;th&gt;transition&lt;/th&gt;
&lt;th&gt;state after&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;a&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;S0&lt;/td&gt;
&lt;td&gt;S0 + &lt;code&gt;a&lt;/code&gt; → S1&lt;/td&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;S1 + &lt;code&gt;b&lt;/code&gt; → S1&lt;/td&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;b&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;S1 + &lt;code&gt;b&lt;/code&gt; → S1&lt;/td&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;c&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;S1 + &lt;code&gt;c&lt;/code&gt; → ACCEPT&lt;/td&gt;
&lt;td&gt;ACCEPT&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here is a graph representation&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F61covji0fgx02prmpolo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F61covji0fgx02prmpolo.png" alt="Finite State Machine that recognizes ab*c" width="720" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Four states, all transitions accounted for, including the error paths to DEAD.&lt;/p&gt;

&lt;p&gt;End of input, state is ACCEPT → &lt;strong&gt;accepted&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Every single line maps to one of the seven primitives. Nothing else is needed.&lt;/p&gt;




&lt;h3&gt;
  
  
  Q: How do we know these seven operators are enough for any FSM? Fact check this!
&lt;/h3&gt;

&lt;p&gt;Yes, the seven operations are sufficient. In fact, they're slightly &lt;strong&gt;redundant&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JMP&lt;/strong&gt; is redundant — it's just &lt;code&gt;BRANCH(always_true, target)&lt;/code&gt;. And &lt;strong&gt;CMP&lt;/strong&gt; can be absorbed into BRANCH (as "branch-on-symbol-match"). And &lt;strong&gt;READ + ADVANCE&lt;/strong&gt; can be fused into one operation.&lt;/p&gt;

&lt;p&gt;So the theoretical minimum is &lt;strong&gt;4 operations&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Minimal op&lt;/th&gt;
&lt;th&gt;Absorbs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;READ/ADVANCE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;READ + ADVANCE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BRANCH-on-match&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;CMP + BRANCH + JMP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ASSIGN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HALT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This matches &lt;strong&gt;Wang's B-machine&lt;/strong&gt; (1957) — a minimal abstract machine with a read-only input tape and finite control that is &lt;em&gt;exactly&lt;/em&gt; a DFA. Its instruction set is: &lt;code&gt;{read, branch-on-symbol, halt-accept, halt-reject}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key references:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Sipser&lt;/strong&gt; (&lt;em&gt;Introduction to the Theory of Computation&lt;/em&gt;, Ch. 1) — treats the transition function &lt;code&gt;δ(state, symbol) → state&lt;/code&gt; as a single atomic primitive. The 7 ops are a decomposition of that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hopcroft/Ullman/Motwani&lt;/strong&gt; (&lt;em&gt;Intro to Automata Theory&lt;/em&gt;, Ch. 2) — same formal treatment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minsky&lt;/strong&gt; (&lt;em&gt;Computation: Finite and Infinite Machines&lt;/em&gt;, 1967, Ch. 3) — decomposes FSMs into read input, compute next-state (combinational logic), latch new state (memory). The hardware view of exactly these ops.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Wang, 1957&lt;/strong&gt; — "A Variant to Turing's Theory of Computing Machines", &lt;em&gt;Journal of the ACM&lt;/em&gt; 4(1). The closest classical reference to a minimal FSM instruction set. &lt;a href="https://dl.acm.org/doi/10.1145/320856.320867" rel="noopener noreferrer"&gt;ACM link&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; The 7 ops are sufficient for any FSM. We keep them separate (rather than using the minimal 4) because they map more clearly to code — each one becomes a distinct, readable operation in the implementation.&lt;/p&gt;




&lt;h3&gt;
  
  
  Q: Why those 7 operators are very much like Assembly operators?
&lt;/h3&gt;

&lt;p&gt;They &lt;em&gt;are&lt;/em&gt; assembly-level operations. That's not a coincidence.&lt;/p&gt;

&lt;p&gt;An FSM is the computational model that sits right above combinational logic. It's the simplest machine that has state. And assembly is the thinnest software abstraction over hardware. So they naturally meet at the same level.&lt;/p&gt;

&lt;p&gt;The mapping is direct:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;FSM op&lt;/th&gt;
&lt;th&gt;x86 equivalent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;READ&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;mov al, [rsi]&lt;/code&gt; (load byte from memory)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CMP&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cmp al, 'a'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BRANCH&lt;/td&gt;
&lt;td&gt;&lt;code&gt;jne label&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JMP&lt;/td&gt;
&lt;td&gt;&lt;code&gt;jmp label&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ASSIGN&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mov state, S1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ADVANCE&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inc rsi&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HALT&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ret&lt;/code&gt; / &lt;code&gt;syscall exit&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Hand-written FSMs in assembly (like in high-performance lexers) look almost identical to our pseudocode. The abstraction gap is essentially zero.&lt;/p&gt;

&lt;p&gt;This is also why FSMs are so fast in practice — every operation maps 1:1 to a single CPU instruction. No function calls, no memory allocation, no indirection. Just compare, branch, assign, repeat.&lt;/p&gt;

&lt;p&gt;When we move to a higher-level language like Python, we'll express these same 7 operations using &lt;code&gt;if/elif&lt;/code&gt;, &lt;code&gt;==&lt;/code&gt;, variable assignment, and a &lt;code&gt;for&lt;/code&gt; loop — but conceptually, it's the same assembly underneath.&lt;/p&gt;




&lt;h3&gt;
  
  
  Q: Can we implement an FSM that recognizes &lt;code&gt;&amp;lt;div&amp;gt;text&amp;lt;/div&amp;gt;&lt;/code&gt; where text is alphanumeric?
&lt;/h3&gt;

&lt;p&gt;Yes. To handle character ranges like &lt;code&gt;[a-z]&lt;/code&gt;, &lt;code&gt;[A-Z]&lt;/code&gt;, &lt;code&gt;[0-9]&lt;/code&gt;, we need to expand CMP and split BRANCH into variants.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Updated operator set (10 operations):&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;th&gt;Why an FSM needs it&lt;/th&gt;
&lt;th&gt;x86 equivalent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;READ&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Get current input symbol&lt;/td&gt;
&lt;td&gt;Consume input&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mov al, [rsi]&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;CMP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Compare two values, set flags&lt;/td&gt;
&lt;td&gt;Every transition needs comparison&lt;/td&gt;
&lt;td&gt;&lt;code&gt;cmp al, 'a'&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BRANCH_EQ&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Jump if equal&lt;/td&gt;
&lt;td&gt;Exact character match&lt;/td&gt;
&lt;td&gt;&lt;code&gt;je label&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BRANCH_NE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Jump if not equal&lt;/td&gt;
&lt;td&gt;Skip when no match&lt;/td&gt;
&lt;td&gt;&lt;code&gt;jne label&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BRANCH_LT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Jump if less than&lt;/td&gt;
&lt;td&gt;Lower bound of range check&lt;/td&gt;
&lt;td&gt;&lt;code&gt;jb label&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BRANCH_GT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Jump if greater than&lt;/td&gt;
&lt;td&gt;Upper bound of range check&lt;/td&gt;
&lt;td&gt;&lt;code&gt;ja label&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JMP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Unconditional jump&lt;/td&gt;
&lt;td&gt;Loop, skip blocks&lt;/td&gt;
&lt;td&gt;&lt;code&gt;jmp label&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ASSIGN&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Write value to register&lt;/td&gt;
&lt;td&gt;Update state&lt;/td&gt;
&lt;td&gt;&lt;code&gt;mov reg, val&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ADVANCE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Move input pointer forward&lt;/td&gt;
&lt;td&gt;Step through input&lt;/td&gt;
&lt;td&gt;&lt;code&gt;inc rsi&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;HALT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Stop execution&lt;/td&gt;
&lt;td&gt;Accept or reject&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;ret&lt;/code&gt; / &lt;code&gt;syscall&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We split the old BRANCH into four variants (EQ, NE, LT, GT). CMP now sets flags rather than returning a boolean — exactly like x86 &lt;code&gt;cmp&lt;/code&gt;. Range checks like &lt;code&gt;[a-z]&lt;/code&gt; become:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight armasm"&gt;&lt;code&gt;&lt;span class="nl"&gt;CMP&lt;/span&gt;       &lt;span class="nb"&gt;char&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'a'&lt;/span&gt;
&lt;span class="nl"&gt;BRANCH_LT&lt;/span&gt; &lt;span class="nb"&gt;fail&lt;/span&gt;        &lt;span class="c"&gt;; char &amp;lt; 'a' → not lowercase&lt;/span&gt;
&lt;span class="nl"&gt;CMP&lt;/span&gt;       &lt;span class="nb"&gt;char&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'z'&lt;/span&gt;
&lt;span class="nl"&gt;BRANCH_GT&lt;/span&gt; &lt;span class="nb"&gt;fail&lt;/span&gt;        &lt;span class="c"&gt;; char &amp;gt; 'z' → not lowercase&lt;/span&gt;
&lt;span class="c"&gt;; if we reach here, 'a' &amp;lt;= char &amp;lt;= 'z'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  FSM for &lt;code&gt;&amp;lt;div&amp;gt;[a-zA-Z0-9]+&amp;lt;/div&amp;gt;&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;States and what has been consumed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Consumed so far&lt;/th&gt;
&lt;th&gt;Expects next&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S0&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(start)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;d&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;i&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;di&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;div&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S5&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;div&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;first alphanumeric&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S6&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;div&amp;gt;text&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;more alphanumeric or &lt;code&gt;&amp;lt;&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S7&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;div&amp;gt;text&amp;lt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S8&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;div&amp;gt;text&amp;lt;/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;d&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S9&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;div&amp;gt;text&amp;lt;/d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;i&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S10&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;div&amp;gt;text&amp;lt;/di&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S11&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;div&amp;gt;text&amp;lt;/div&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ACCEPT&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;div&amp;gt;text&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(end of input)&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DEAD&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(error)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(absorb remaining)&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi7o1b688o84dq1dj851i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi7o1b688o84dq1dj851i.png" alt="Finite State Machine that recognizes &amp;lt;div&amp;gt;" width="800" height="487"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pseudocode using the 10 operators:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight armasm"&gt;&lt;code&gt;    &lt;span class="nb"&gt;ASSIGN&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S0&lt;/span&gt;

&lt;span class="nl"&gt;LOOP&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;; ── DEAD: absorb remaining input ──&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;DEAD&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_EQ&lt;/span&gt; &lt;span class="nv"&gt;next&lt;/span&gt;

    &lt;span class="c"&gt;; ── ACCEPT + more input → error ──&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ACCEPT&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_EQ&lt;/span&gt; &lt;span class="nv"&gt;to_dead&lt;/span&gt;

    &lt;span class="nb"&gt;READ&lt;/span&gt;   &lt;span class="nv"&gt;char&lt;/span&gt;

    &lt;span class="c"&gt;; ── S0: expecting '&amp;lt;' ──&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S0&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;not_s0&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'&amp;lt;'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;to_dead&lt;/span&gt;
    &lt;span class="nb"&gt;ASSIGN&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S1&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;next&lt;/span&gt;

  &lt;span class="nf"&gt;not_s0&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;; ── S1: expecting 'd' ──&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S1&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;not_s1&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'d'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;to_dead&lt;/span&gt;
    &lt;span class="nb"&gt;ASSIGN&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S2&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;next&lt;/span&gt;

  &lt;span class="nf"&gt;not_s1&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;; ── S2: expecting 'i' ──&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S2&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;not_s2&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'i'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;to_dead&lt;/span&gt;
    &lt;span class="nb"&gt;ASSIGN&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S3&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;next&lt;/span&gt;

  &lt;span class="nf"&gt;not_s2&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;; ── S3: expecting 'v' ──&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S3&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;not_s3&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'v'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;to_dead&lt;/span&gt;
    &lt;span class="nb"&gt;ASSIGN&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S4&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;next&lt;/span&gt;

  &lt;span class="nf"&gt;not_s3&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;; ── S4: expecting '&amp;gt;' ──&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S4&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;not_s4&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'&amp;gt;'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;to_dead&lt;/span&gt;
    &lt;span class="nb"&gt;ASSIGN&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S5&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;next&lt;/span&gt;

  &lt;span class="nf"&gt;not_s4&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;; ── S5: expecting first alphanumeric ──&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S5&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;not_s5&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;check_alnum_s6&lt;/span&gt;     &lt;span class="c"&gt;; reuse alnum check, go to S6&lt;/span&gt;

  &lt;span class="nf"&gt;not_s5&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;; ── S6: more alphanumeric or '&amp;lt;' ──&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S6&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;not_s6&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'&amp;lt;'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;s6_alnum&lt;/span&gt;
    &lt;span class="nb"&gt;ASSIGN&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S7&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;next&lt;/span&gt;
  &lt;span class="nf"&gt;s6_alnum&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;check_alnum_stay&lt;/span&gt;   &lt;span class="c"&gt;; stay in S6&lt;/span&gt;

  &lt;span class="nf"&gt;not_s6&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;; ── S7: expecting '/' ──&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S7&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;not_s7&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'/'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;to_dead&lt;/span&gt;
    &lt;span class="nb"&gt;ASSIGN&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S8&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;next&lt;/span&gt;

  &lt;span class="nf"&gt;not_s7&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;; ── S8: expecting 'd' ──&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S8&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;not_s8&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'d'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;to_dead&lt;/span&gt;
    &lt;span class="nb"&gt;ASSIGN&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S9&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;next&lt;/span&gt;

  &lt;span class="nf"&gt;not_s8&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;; ── S9: expecting 'i' ──&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S9&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;not_s9&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'i'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;to_dead&lt;/span&gt;
    &lt;span class="nb"&gt;ASSIGN&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S10&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;next&lt;/span&gt;

  &lt;span class="nf"&gt;not_s9&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;; ── S10: expecting 'v' ──&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S10&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;not_s10&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'v'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;to_dead&lt;/span&gt;
    &lt;span class="nb"&gt;ASSIGN&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S11&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;next&lt;/span&gt;

  &lt;span class="nf"&gt;not_s10&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="c"&gt;; ── S11: expecting '&amp;gt;' ──&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S11&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;to_dead&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'&amp;gt;'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;to_dead&lt;/span&gt;
    &lt;span class="nb"&gt;ASSIGN&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ACCEPT&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;next&lt;/span&gt;

    &lt;span class="c"&gt;; ── alphanumeric check → transition to S6 ──&lt;/span&gt;
  &lt;span class="nf"&gt;check_alnum_s6&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'a'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_LT&lt;/span&gt; &lt;span class="nv"&gt;upper_s6&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'z'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_GT&lt;/span&gt; &lt;span class="nv"&gt;upper_s6&lt;/span&gt;
    &lt;span class="nb"&gt;ASSIGN&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S6&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;next&lt;/span&gt;
  &lt;span class="nf"&gt;upper_s6&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'A'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_LT&lt;/span&gt; &lt;span class="nv"&gt;digit_s6&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'Z'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_GT&lt;/span&gt; &lt;span class="nv"&gt;digit_s6&lt;/span&gt;
    &lt;span class="nb"&gt;ASSIGN&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S6&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;next&lt;/span&gt;
  &lt;span class="nf"&gt;digit_s6&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'0'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_LT&lt;/span&gt; &lt;span class="nv"&gt;to_dead&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'9'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_GT&lt;/span&gt; &lt;span class="nv"&gt;to_dead&lt;/span&gt;
    &lt;span class="nb"&gt;ASSIGN&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;S6&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;next&lt;/span&gt;

    &lt;span class="c"&gt;; ── alphanumeric check → stay in S6 ──&lt;/span&gt;
  &lt;span class="nf"&gt;check_alnum_stay&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'a'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_LT&lt;/span&gt; &lt;span class="nv"&gt;upper_stay&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'z'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_GT&lt;/span&gt; &lt;span class="nv"&gt;upper_stay&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;next&lt;/span&gt;
  &lt;span class="nf"&gt;upper_stay&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'A'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_LT&lt;/span&gt; &lt;span class="nv"&gt;digit_stay&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'Z'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_GT&lt;/span&gt; &lt;span class="nv"&gt;digit_stay&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;next&lt;/span&gt;
  &lt;span class="nf"&gt;digit_stay&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'0'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_LT&lt;/span&gt; &lt;span class="nv"&gt;to_dead&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;char&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'9'&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_GT&lt;/span&gt; &lt;span class="nv"&gt;to_dead&lt;/span&gt;
    &lt;span class="nb"&gt;JMP&lt;/span&gt;    &lt;span class="nv"&gt;next&lt;/span&gt;

  &lt;span class="nf"&gt;to_dead&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;ASSIGN&lt;/span&gt; &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;DEAD&lt;/span&gt;

  &lt;span class="nb"&gt;next&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;ADVANCE&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;input_end&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;true&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;LOOP&lt;/span&gt;

    &lt;span class="c"&gt;; ── input exhausted ──&lt;/span&gt;
    &lt;span class="nb"&gt;CMP&lt;/span&gt;    &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ACCEPT&lt;/span&gt;
    &lt;span class="nf"&gt;BRANCH_NE&lt;/span&gt; &lt;span class="nv"&gt;reject&lt;/span&gt;
    &lt;span class="nb"&gt;HALT&lt;/span&gt;   &lt;span class="nv"&gt;accept&lt;/span&gt;

  &lt;span class="nb"&gt;reject&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;HALT&lt;/span&gt;   &lt;span class="nv"&gt;reject&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Trace with input &lt;code&gt;&amp;lt;div&amp;gt;hello&amp;lt;/div&amp;gt;&lt;/code&gt;:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;char&lt;/th&gt;
&lt;th&gt;state&lt;/th&gt;
&lt;th&gt;transition&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;S0 → S1&lt;/td&gt;
&lt;td&gt;exact match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;S1 → S2&lt;/td&gt;
&lt;td&gt;exact match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;i&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;S2 → S3&lt;/td&gt;
&lt;td&gt;exact match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;S3 → S4&lt;/td&gt;
&lt;td&gt;exact match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;S4 → S5&lt;/td&gt;
&lt;td&gt;exact match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;&lt;code&gt;h&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;S5 → S6&lt;/td&gt;
&lt;td&gt;range: &lt;code&gt;'a'&lt;/code&gt; ≤ &lt;code&gt;'h'&lt;/code&gt; ≤ &lt;code&gt;'z'&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;&lt;code&gt;e&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;S6 → S6&lt;/td&gt;
&lt;td&gt;range: &lt;code&gt;'a'&lt;/code&gt; ≤ &lt;code&gt;'e'&lt;/code&gt; ≤ &lt;code&gt;'z'&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;&lt;code&gt;l&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;S6 → S6&lt;/td&gt;
&lt;td&gt;range: &lt;code&gt;'a'&lt;/code&gt; ≤ &lt;code&gt;'l'&lt;/code&gt; ≤ &lt;code&gt;'z'&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;&lt;code&gt;l&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;S6 → S6&lt;/td&gt;
&lt;td&gt;range: &lt;code&gt;'a'&lt;/code&gt; ≤ &lt;code&gt;'l'&lt;/code&gt; ≤ &lt;code&gt;'z'&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;&lt;code&gt;o&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;S6 → S6&lt;/td&gt;
&lt;td&gt;range: &lt;code&gt;'a'&lt;/code&gt; ≤ &lt;code&gt;'o'&lt;/code&gt; ≤ &lt;code&gt;'z'&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;lt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;S6 → S7&lt;/td&gt;
&lt;td&gt;exact match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;S7 → S8&lt;/td&gt;
&lt;td&gt;exact match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;&lt;code&gt;d&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;S8 → S9&lt;/td&gt;
&lt;td&gt;exact match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;&lt;code&gt;i&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;S9 → S10&lt;/td&gt;
&lt;td&gt;exact match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;&lt;code&gt;v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;S10 → S11&lt;/td&gt;
&lt;td&gt;exact match&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;&lt;code&gt;&amp;gt;&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;S11 → ACCEPT&lt;/td&gt;
&lt;td&gt;exact match&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;End of input, state is ACCEPT → &lt;strong&gt;accepted&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  What's next
&lt;/h3&gt;

&lt;p&gt;At this point our FSM can &lt;em&gt;recognize&lt;/em&gt; whether a string like &lt;code&gt;&amp;lt;div&amp;gt;hello&amp;lt;/div&amp;gt;&lt;/code&gt; is valid, but it can only answer yes or no. A real tokenizer needs to do more than accept or reject. It needs to &lt;strong&gt;emit tokens&lt;/strong&gt;: structured data that tells the next stage of the parser &lt;em&gt;what&lt;/em&gt; was found and &lt;em&gt;where&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That means we need a data structure to hold the tokenizer's output — something that captures the tag names, the text content, and the boundaries between them. In the next post, we'll design that output format and turn our recognizer into a proper tokenizer that produces a stream of tokens.&lt;/p&gt;

&lt;p&gt;Stay tuned...&lt;/p&gt;

</description>
      <category>parsing</category>
      <category>computerscience</category>
      <category>programming</category>
      <category>learning</category>
    </item>
  </channel>
</rss>
