Home · ASPLOS 2026 · Compilers & Programming Languages

Static Analysis for Efficient Streaming Tokenization

Angela W. Li, Yudi Yang and Konstantinos Mamouras

Conference
ASPLOS 2026
Published
2026
Pages
1880-1896
Citations
1

Tokenization splits text into tokens (numbers, identifiers, strings) using a grammar of regular expressions and a longest-match rule, and it is the first step in compiling code and in processing semi-structured data like JSON, CSV, and XML; when that data is too big for memory you want to tokenize in a streaming fashion with a tiny footprint, but the longest-match rule can force a tokenizer to hold back and look far ahead before it can commit to a token, and standard tools like flex handle this with costly backtracking; this paper (Rice University — Angela W. Li, Yudi Yang, Konstantinos Mamouras) shows that some grammars are inherently harder than others, gives a static analysis to tell which, and uses it to build StreamTok, a streaming tokenizer that is asymptotically faster than flex's algorithm and beats state-of-the-art tools.

The argument in one glance

  1. Problem

    Splitting huge text into tokens should stream, but the longest-match rule fights streaming

    Tokenization — also called lexing or scanning — is the task of chopping an input text into a sequence of substrings called tokens, each belonging to a class like integer, floating-point number, identifier, or string literal. It is the first stage of compiling a program, a step in natural language processing, and a common front end for processing semi-structured data such as JSON, CSV, and XML before parsing or querying it. A tokenizer is specified by a tokenization grammar: a list of regular expressions, one per token class. To decide where one token ends and the next begins, tokenization uses the longest-match (or maximal-munch) policy: at each position, prefer the longest substring that matches some token class. This policy is simple to state but has a subtle cost — to know a token has truly ended, you may have to look ahead to be sure no longer token could still be forming. When the data is large, you want to tokenize in a streaming fashion with a small memory footprint, and that lookahead requirement is exactly what makes streaming hard.

  2. What people did

    Use a standard lexer generator like flex

    The classic solution is a lexer generator such as flex, which compiles the tokenization grammar into a finite automaton and scans the input, using the longest-match policy. When the automaton runs past the end of the current token looking for a possibly-longer match and does not find one, it backtracks to the last accepting position and emits that token, then resumes. This is the standard, widely used way to build tokenizers.

  3. Why it fell short

    Backtracking can force unbounded lookahead and large buffers

    The trouble is that the backtracking flex relies on can be expensive, and for some grammars pathologically so. Because maximal munch requires confirming that no longer token could match, the scanner may run far past a valid token boundary before it can commit, and if it then has to backtrack it must re-scan the characters it overran — and it must have kept those characters buffered, since in a stream they cannot be re-read from a file cheaply. For certain grammars this lookahead is not bounded by a constant: the amount the tokenizer must buffer before it can emit anything can grow with the input, which both blows up the memory footprint (defeating the point of streaming) and makes the time asymptotically worse than a single linear pass. So the standard algorithm is a poor fit for streaming tokenization of large data, and crucially it treats all grammars the same, without recognizing which ones actually cause this blow-up and which do not.

  4. This paper's idea

    Analyze which grammars are hard, then stream accordingly

    This paper makes two moves. First, it observes that some tokenization grammars are inherently more difficult to stream than others — depending on how their regular expressions interact under maximal munch, some require unbounded lookahead and buffering while others can be tokenized with a small, bounded footprint — and it provides a static analysis algorithm that examines a grammar and recognizes which category it falls into, before any input is seen. Second, building on that analysis, it proposes StreamTok, a tokenization algorithm that uses the grammar's analyzed structure to tokenize efficiently in a streaming fashion, avoiding the pathological backtracking and buffering that the standard approach incurs. StreamTok is asymptotically better than the standard algorithm of flex, and in experiments the authors' implementation outperforms state-of-the-art tokenization tools. In short, by first understanding the grammar statically, StreamTok streams the easy cases cheaply and handles the hard ones without the unbounded blow-up that flex suffers.

Why this problem is worth solving

Tokenization is a small word for a task that sits under an enormous amount of computing, so making it fast and frugal has broad reach.

Tokenization, also known as lexing or scanning, partitions an input text into a sequence of tokens — meaningful substrings each belonging to a class such as integer, floating-point number, variable identifier, or string literal. It is the first stage of program compilation, where source code is turned into a token stream before parsing; it appears in natural language processing; and it is a workhorse for handling unstructured text and semi-structured data formats like JSON, CSV, and XML, where tokenizing precedes parsing, querying, or transforming the data. The tokenizer is specified declaratively as a tokenization grammar: a list of regular expressions, one describing each class of token. Because a single position in the text might be the start of several possible tokens, tokenization needs a rule to choose, and the standard rule is the longest-match or maximal-munch policy — always prefer the longest substring that matches. This declarative, regex-plus-longest-match model is elegant and ubiquitous, which is exactly why its efficiency matters: any inefficiency in tokenization is paid across compilers, data pipelines, and text processors everywhere. And as the volume of semi-structured data has exploded — logs, records, feeds too large to fit in memory — the way tokenization uses time and especially memory has become a real bottleneck, not a rounding error, which is what makes revisiting how tokenization is implemented worthwhile.

The reason streaming tokenization is genuinely hard is that the longest-match rule and streaming pull in opposite directions.

Streaming means processing the input in one pass with a small, bounded amount of memory, emitting output as you go without holding the whole input. Maximal munch is at odds with this because of what it demands: to emit a token, you must be sure it is the longest possible match starting at that position, and being sure can require reading further. Suppose you have matched a valid token, but the grammar allows a longer token that shares this prefix; you cannot emit yet, because a few more characters might extend the match. So you read ahead, keeping the characters you have consumed in a buffer in case you must fall back. If a longer match materializes, good; if not, you backtrack to the last valid token and re-process from there. For many grammars this lookahead is short and bounded, so the buffer stays small. But for others, the interaction of the token regular expressions is such that you might have to look ahead an amount that grows with the input before you can commit to a single token — and then you must have buffered all of it, and re-scanning it makes the total work super-linear. That is the crux: maximal munch can turn a one-pass, small-memory task into one that needs unbounded buffering and re-scanning, and whether it does depends entirely on the grammar. A tokenizer that does not know which grammars are dangerous must be conservative for all of them, which is the inefficiency worth removing.

What people did before, and where it broke

  • Load the whole input and tokenize in memory. Read the entire text into memory, then run the tokenizer with full random access. Where it broke. Large-scale semi-structured data is often too big to fit in memory, so buffering all of it is infeasible, defeating the goal of a small footprint.
  • Standard lexer generators (flex). Compile the grammar to an automaton and scan with longest-match, backtracking to the last accepting state when a longer match fails. Where it broke. The backtracking can require unbounded lookahead and buffering for certain grammars, blowing up memory and making the time asymptotically worse than a linear pass.
  • Treat all grammars uniformly. Apply the same scanning-and-backtracking strategy regardless of the grammar. Where it broke. It cannot tell which grammars actually cause unbounded lookahead and which do not, so it pays a worst-case-shaped cost even for grammars that could stream cheaply.
  • Restrict the grammar by hand. Ask users to write only grammars that avoid problematic lookahead. Where it broke. This shifts a subtle burden onto the user, who has no principled way to know which grammars are safe, and it forbids legitimate grammars that could be handled well.
  • The gap. No approach first analyzed a tokenization grammar statically to determine whether it can be streamed with bounded memory, and then used that knowledge to tokenize with an algorithm asymptotically better than flex’s while keeping a small footprint.

How StreamTok works

Analyzing a grammar, then streaming its tokenization efficiently

  1. new

  2. new

  3. new

  4. new

  5. new

The idea

Understand the grammar before you run, so you can stream the right way. The organizing move in this work is to shift effort to a static analysis that happens once, on the grammar, before any input arrives, rather than coping with difficulty blindly at run time. The standard tokenizer treats every grammar the same and discovers the cost of lookahead only while scanning; StreamTok instead recognizes, from the grammar’s structure alone, whether tokenizing it can be streamed with bounded memory, and tailors its behavior accordingly. Analyzing a program or specification ahead of time to learn a structural property that then unlocks an efficient execution strategy is the same discipline the work on precise invariant-guided pointer analysis covered elsewhere on this site is built on, extracting a static fact that makes the subsequent processing sound and fast. StreamTok applies that discipline to tokenization grammars, turning a property of the regular expressions into a decision about how to stream.

Stream large semi-structured data with a small footprint. The practical goal is to tokenize data too large to fit in memory in one pass, emitting tokens as they are recognized without buffering the whole input. This is exactly the regime where the longest-match lookahead problem bites, because unbounded buffering would erase the memory savings streaming is supposed to give. StreamTok’s analysis is what makes small-footprint streaming possible for the grammars that admit it, and honest about the ones that do not. Processing semi-structured data as a stream, with bounded memory, rather than loading it all at once is the same design goal the work on streaming semi-structured data with bit-parallel fast-forwarding covered elsewhere on this site pursues, handling formats like JSON in a single efficient pass. StreamTok addresses the tokenization layer beneath such processing, where the maximal-munch rule makes streaming subtle.

Beat the worst case by avoiding it, not enduring it. The efficiency win is asymptotic: flex’s standard algorithm can be forced into super-linear re-scanning because it backtracks reactively, whereas StreamTok, knowing the grammar, commits to boundaries as early as safe and never performs the unbounded overrun-and-rescan. It is the difference between an algorithm that pays the worst case and one whose analysis lets it sidestep the worst case where possible. Doing less redundant work by not re-processing input the system can prove it will not need is the same efficiency instinct that streaming systems built to handle data too large for memory rely on, and the work on streaming point-cloud analytics via compulsory splitting covered elsewhere on this site embodies it, keeping the pass bounded by splitting work rather than re-traversing it. StreamTok keeps tokenization bounded by resolving, in advance, how far it must ever look.

Following a grammar through StreamTok

Tokenizing a large JSON-like stream with a small footprint

An illustrative trace, based on the paper's abstract, of how StreamTok analyzes a grammar and streams. Internal details beyond the abstract were not available for this draft.

Step 1 of 5

Provide the tokenization grammar

The user supplies a grammar as a list of regular expressions, one per token class, to be applied under the longest-match policy. This grammar will be used to tokenize a large stream of semi-structured text that cannot fit in memory.

The grammar, not the data, is what StreamTok analyzes first.

Why it works

  • Static analysis moves the hard thinking off the hot path. The standard tokenizer rediscovers lookahead cost on every input at run time → but StreamTok analyzes the grammar once, before any data → so the per-input work is guided by a fact computed exactly once.
  • Knowing the grammar lets the tokenizer commit early. Blindly, a scanner must overrun to be sure a token is longest → but with the analyzed structure StreamTok knows when no longer match is possible → so it commits to boundaries as early as safe and stops overrunning.
  • Bounded lookahead means a bounded buffer. Unbounded overrun forces buffering input that grows with the data → but committing early caps how far ahead StreamTok must look → so the memory footprint stays small, which is what streaming requires.
  • Avoiding re-scanning beats the worst case asymptotically. Flex’s backtracking can re-process overrun characters, making time super-linear → but StreamTok never performs that unbounded re-scan → so its time complexity is asymptotically better than the standard algorithm.
  • Classifying grammars avoids one-size-fits-all conservatism. Treating every grammar as worst-case wastes effort on easy ones → but recognizing which grammars are difficult lets StreamTok stream the easy ones cheaply → so common grammars run fast instead of paying for rare pathologies.

What it costs (trade-offs and scope)

  • This is a draft built from the paper’s abstract. The full paper was behind a publisher access wall for this draft (no arXiv version, no open-access or author copy located, and the ACM Digital Library returned a bot-verification challenge), so this account is assembled from the paper’s complete abstract plus standard background on tokenization, maximal munch, and streaming. The exact static-analysis algorithm, the precise definition of a difficult grammar, the StreamTok algorithm’s details, its complexity bounds, and the evaluation are not reproduced here and should be read from the paper.
  • Difficult grammars remain fundamentally hard. The analysis recognizes grammars that inherently require unbounded lookahead, but recognizing them does not make them streamable with bounded memory for free; the benefit is streaming the tractable grammars efficiently and handling the hard ones without pathological blow-up, not eliminating the difficulty the maximal-munch semantics create.
  • The static analysis has its own cost. Analyzing the grammar is extra work before tokenizing; this is a one-time cost amortized over processing large inputs, and the abstract does not, in what is available here, detail its complexity or how it scales with grammar size.
  • The gains are about asymptotics and specific experiments. The claim that StreamTok is asymptotically better than flex and outperforms state-of-the-art tools is the abstract’s own; the specific inputs, grammars, baselines, and margins behind the experimental results are not detailed in this draft.
  • It targets streaming tokenization of large semi-structured and text data. The setting is single-pass, small-footprint tokenization; scenarios where the input fits comfortably in memory, or where random access is available, may not see the same advantage, and the approach is about the tokenization stage rather than the parsing or querying that follows.

The evaluation

Tokenizing large data under the longest-match policy

PropertyIn-memory tokenizationflex (standard backtracking)StreamTok
Memory footprint
Handles data too big for memory
Knows which grammars are hard
Worst-case time
Reported outcome (per abstract)
A qualitative comparison based on the paper's abstract; the paper's main body, including the static-analysis algorithm, the StreamTok algorithm, complexity bounds, and the evaluation, was not available for this draft. The 'asymptotically better than flex' and 'outperforms state-of-the-art tools' claims are the abstract's own.Underlined values explain themselves when clicked.

Check yourself

Check your understanding

  1. 1. Why is streaming tokenization hard under the longest-match policy?

  2. 2. What are the two main contributions of the paper?

  3. 3. How does StreamTok achieve better asymptotic performance than flex's standard algorithm?

  4. 4. What should a reader keep in mind about this explainer, and what did the paper report?

Sources

The paper is “Static Analysis for Efficient Streaming Tokenization” by Angela W. Li, Yudi Yang, and Konstantinos Mamouras (Rice University), published at ASPLOS 2026 (Volume 2), DOI 10.1145/3779212.3790227. The algorithm is named StreamTok. It is licensed CC BY.

This explainer is a draft. The paper’s main text was not accessible at the time of writing (there is no arXiv preprint, no open-access or author copy was located, and the ACM Digital Library PDF returned a bot-verification challenge), so it is written from the paper’s complete abstract together with standard background on tokenization, the maximal-munch policy, lexer generators such as flex, and streaming. The abstract supplies the problem and approach: tokenization (lexing or scanning) partitions an input text into tokens, is a first stage of program compilation, is used in natural language processing, and is useful for processing unstructured text or semi-structured data such as JSON, CSV, and XML; a tokenizer is specified as a tokenization grammar (a list of regular expressions describing token classes), and the semantics use the longest-match (maximal-munch) policy to disambiguate; because large-scale semi-structured data can be too large to load in memory, streaming tokenization with a small memory footprint is desirable; the paper observes that some tokenization grammars are inherently more difficult to deal with than others and provides a static analysis algorithm for recognizing them; it proposes the StreamTok algorithm, which relies on this analysis to enable efficient tokenization; StreamTok is asymptotically better than the standard algorithm of flex; and experimental results show that the implementation of StreamTok outperforms state-of-the-art tools for tokenization. The explanations of why maximal munch forces lookahead and why that breaks streaming are standard background and the natural reading of the abstract, not claims about StreamTok’s specific internal design. The paper’s exact static-analysis algorithm, the definition of a difficult grammar, the StreamTok algorithm, its complexity bounds, and the evaluation are not reproduced here and should be read from the paper. No numbers beyond the abstract’s have been introduced.

Related papers on Compilers & Programming Languages

See all 162 →