Skip to main content

External Scanners

Many languages have tokens whose structure is impossible or inconvenient to describe with regular expressions. External scanners allow you to write custom C code to handle these cases.

When to Use External Scanners

Common use cases:

Indentation Tokens

Python’s INDENT/DEDENT tokens based on whitespace

Heredocs

Multi-line string literals in Bash and Ruby

Percent Strings

Ruby’s %q, %w[], and other percent literals

Context-Sensitive Tokens

Tokens that depend on parsing state
Only use external scanners when regular expressions cannot handle the token. External scanners add complexity and must be carefully implemented to avoid bugs.

Setup

1. Declare External Tokens

Add an externals section to your grammar:
grammar.js

2. Create Scanner File

Create src/scanner.c in your project:
src/scanner.c
The enum order must exactly match the order in your grammar’s externals array. The names can be different but the order is critical.

Required Functions

You must implement five functions with specific names based on your language:

Create

Allocate and initialize your scanner state:
Use ts_malloc, ts_calloc, and ts_free instead of libc functions to allow custom allocators.

Destroy

Free any allocated memory:

Serialize

Save scanner state to a byte buffer:
The maximum buffer size is TREE_SITTER_SERIALIZATION_BUFFER_SIZE. Design your state to fit within this limit.

Deserialize

Restore scanner state from a byte buffer:
Always clear state at the start of deserialize before restoring values.

Scan

Recognize tokens and return results:

The Scan Function

TSLexer Interface

The TSLexer struct provides:
int32_t
Current character as a 32-bit Unicode code point
TSSymbol
Set this to the recognized token type
function
void (*advance)(TSLexer *, bool skip)Advance to next character. Pass true to skip whitespace.
function
void (*mark_end)(TSLexer *)Mark the end of the token. Allows lookahead without consuming characters.
function
uint32_t (*get_column)(TSLexer *)Get current column position (codepoints since line start)
function
bool (*eof)(const TSLexer *)Check if at end of file
function
bool (*is_at_included_range_start)(const TSLexer *)Check if parser skipped to a new range (for multi-language documents)

Basic Pattern

1

Check valid_symbols

Only look for tokens that are valid at this position.
2

Advance through characters

Call lexer->advance() to consume characters.
3

Mark end (optional)

Call lexer->mark_end() to enable lookahead.
4

Set result_symbol

Set lexer->result_symbol to the token type.
5

Return true

Return true if a token was recognized, false otherwise.

Example: String Scanner

Example: Python Indentation

Helper Utilities

Tree-sitter Allocator

Use these instead of libc functions:
To enable custom allocators, compile with -DTREE_SITTER_REUSE_ALLOCATOR and link the library dynamically.

Array Helpers

Use the array macros from tree_sitter/array.h:
Do not use underscore-prefixed array functions. They are internal helpers.

Error Recovery

During error recovery, Tree-sitter calls your scanner with all tokens marked as valid.

Detect Error Recovery

Add an error sentinel token:
Check in your scanner:

External Keywords

You can include literal keywords in externals:
This makes the external scanner responsible for tokenizing these keywords. Equivalent to:

Priority and Interaction

External scanners have priority over Tree-sitter’s normal lexing. When an external token is valid, the scanner is called first.

Fallback Behavior

1

Scanner called

External scanner attempts to recognize the token.
2

Scanner returns true

The scanned token is used.
3

Scanner returns false

Tree-sitter falls back to internal lexer (for literal keywords only).

Common Pitfalls

Infinite loops: External scanners can easily create infinite loops. Always use eof() in loops and never emit zero-width tokens carelessly.

Zero-Width Tokens

Be extremely careful with zero-width tokens. They can cause infinite loops if the parser keeps asking for the same token at the same position.

Complete Example

Here’s a complete external scanner for a simple indentation-based language:
src/scanner.c

Next Steps

Now that you understand external scanners: