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
Setup
1. Declare External Tokens
Add anexternals section to your grammar:
grammar.js
2. Create Scanner File
Createsrc/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:Destroy
Free any allocated memory:Serialize
Save scanner state to a byte buffer:Deserialize
Restore scanner state from a byte buffer:Scan
Recognize tokens and return results:The Scan Function
TSLexer Interface
TheTSLexer 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 filefunction
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 fromtree_sitter/array.h:
Error Recovery
During error recovery, Tree-sitter calls your scanner with all tokens marked as valid.Detect Error Recovery
Add an error sentinel token:External Keywords
You can include literal keywords inexternals:
Priority and Interaction
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
Zero-Width Tokens
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:- Write comprehensive Tests for your scanner
- Learn about Publishing your parser
- See real examples in tree-sitter-python