lexer.ts

Flat-event lexer substrate — the engine beneath the per-language lexers (lexer_json.ts, lexer_ts.ts, …).

Tokens are emitted as variable-length records into one Int32Array:

  • leaf: [type_id, start, end] where type_id > 0
  • open: [-type_id, start] — opens a container token
  • close: [0, end] — closes the innermost open container

Untyped text is implicit — it's the gap between events, recovered from offsets. Adjacent same-type leaves are coalesced at emit time so runs like }); become one span.

view source

Declarations
#

33 declarations

advance_probe
#

lexer.ts view source

(text: string, cached: number, from: number, ch: string): number import {advance_probe} from '@fuzdev/fuz_code/lexer.js';

Returns the cached next occurrence of ch at or after from, re-probing with indexOf only when the cached position has fallen behind. PROBE_NOT_FOUND when the text has no further occurrence — a monotonic probe that keeps delimiter scans linear across a construct that is dense in ch.

text

type string

cached

type number

from

type number

ch

type string

returns

number

copy_lexed_syntax
#

lexer.ts view source

(lexed: LexedSyntax): LexedSyntax import {copy_lexed_syntax} from '@fuzdev/fuz_code/lexer.js';

Copies a lexed event stream out of the buffer lex_syntax reuses, sized to events_len, for a result kept past the next lex.

lexed

returns

LexedSyntax

EMBED_UNAVAILABLE
#

lexer.ts view source

-2 import {EMBED_UNAVAILABLE} from '@fuzdev/fuz_code/lexer.js';

Returned by Lexer.embed_to_brace when it lexed nothing.

is_ascii_alnum
#

lexer.ts view source

(c: number): boolean import {is_ascii_alnum} from '@fuzdev/fuz_code/lexer.js';

c

type number

returns

boolean

is_ascii_word
#

lexer.ts view source

(c: number): boolean import {is_ascii_word} from '@fuzdev/fuz_code/lexer.js';

A \w word char — ASCII letters, digits, and _.

c

type number

returns

boolean

is_digit
#

lexer.ts view source

(c: number): boolean import {is_digit} from '@fuzdev/fuz_code/lexer.js';

c

type number

returns

boolean

is_hex_digit
#

lexer.ts view source

(c: number): boolean import {is_hex_digit} from '@fuzdev/fuz_code/lexer.js';

c

type number

returns

boolean

is_ident
#

lexer.ts view source

(c: number): boolean import {is_ident} from '@fuzdev/fuz_code/lexer.js';

c

type number

returns

boolean

is_ident_start
#

lexer.ts view source

(c: number): boolean import {is_ident_start} from '@fuzdev/fuz_code/lexer.js';

c

type number

returns

boolean

is_space
#

lexer.ts view source

(c: number): boolean import {is_space} from '@fuzdev/fuz_code/lexer.js';

c

type number

returns

boolean

is_upper
#

lexer.ts view source

(c: number): boolean import {is_upper} from '@fuzdev/fuz_code/lexer.js';

c

type number

returns

boolean

lex_syntax
#

lexer.ts view source

(text: string, lang: SyntaxLang, langs?: Map<string, SyntaxLang> | undefined, types?: TokenTypeRegistry): LexedSyntax import {lex_syntax} from '@fuzdev/fuz_code/lexer.js';

Lexes text with lang, returning the flat event stream.

The result is valid until the next lex_syntax call: every call writes its events into one reused buffer, which is also why events runs past events_len. Use the result right away, or keep a copy with copy_lexed_syntax. A buffer allocated per call would be off-heap memory, zero-filled up front and freed only at a later GC, which on small inputs costs more than the lexing.

text

type string

lang

langs?

registry used to resolve embedded languages by id

type Map<string, SyntaxLang>
optional

types

token-type registry stamped on the result; must be the one lang (and any embedded language) interned its type ids into

default token_types_global

returns

LexedSyntax

LexedSyntax
#

lexer.ts view source

LexedSyntax import type {LexedSyntax} from '@fuzdev/fuz_code/lexer.js';

The result of lexing: the source text plus its flat event stream.

text

type string

events

type Int32Array<ArrayBufferLike>

events_len

type number

types

The registry that interned the type ids in events — consumers resolve ids through it, so a stream can never be rendered against the wrong id space.

type TokenTypeRegistry

Lexer
#

lexer.ts view source

import {Lexer} from '@fuzdev/fuz_code/lexer.js';

Shared lexing context passed to language lex functions. Holds the text window, the event buffer, and the language registry for embedding.

text

type string

pos

type number

end

type number

langs

Language registry for embed — set by lex_syntax.

type Map<string, SyntaxLang> | null

events

type Int32Array<ArrayBufferLike>

events_len

type number

shape_anchor

One instance alive for the class's lifetime. V8 holds the hidden class of an initialized Lexer only through live instances, and lex_syntax makes one per call, so without it a major GC between calls clears the class and discards every optimized lexer function that embeds it. Every field is declared here, so this instance's hidden class is the one every lexer has. A static rather than a module variable: V8 frees a module variable that no function reads once the module has evaluated, and bundlers drop an unused export.

type Lexer

staticreadonly

internal

constructor

type new (events?: Int32Array<ArrayBufferLike>): Lexer

events

the buffer to emit into from index 0; #grow replaces it with a larger copy when it fills

type Int32Array<ArrayBufferLike>
default new Int32Array(256)

leaf

Emits a leaf token. Empty spans are dropped; a leaf adjacent to a preceding leaf of the same type extends it instead (span coalescing).

type (type_id: number, start: number, end: number): void

type_id

type number

start

type number

end

type number
returns void

open

Opens a container token at start. Must be balanced by a later close.

type (type_id: number, start: number): void

type_id

type number

start

type number
returns void

close

Closes the innermost open container at end.

type (end: number): void

end

type number
returns void

embed

Lexes [start, end) with the language registered as lang_id, restoring this lexer's window afterward. Returns false (leaving the region as plain text) when the language isn't registered or embedding is nested past MAX_EMBED_DEPTH.

type (lang_id: string, start: number, end: number): boolean

lang_id

type string

start

type number

end

type number
returns boolean

embed_to_brace

Lexes an embedded region that starts at start and ends at its closing }, somewhere before end, with the language registered under lang_id, which finds the brace through SyntaxLang.lex_to_brace. Returns the brace's index, or -1 when the region ran to end without one, or EMBED_UNAVAILABLE when nothing was lexed: the language is unregistered or has no lex_to_brace, or nesting is at MAX_EMBED_DEPTH. With a stop_word the region also ends before that word at its top level, and the word's index is returned.

type (lang_id: string, start: number, end: number, stop_word?: string | undefined): number

lang_id

type string

start

type number

end

type number

stop_word?

type string
optional
returns number

matches_ci
#

lexer.ts view source

(text: string, from: number, word: string): boolean import {matches_ci} from '@fuzdev/fuz_code/lexer.js';

Case-insensitive ASCII match of word (must be lowercase) at text[from..], via code | 0x20 folding — never allocates, unlike toLowerCase() comparisons.

text

type string

from

type number

word

type string

returns

boolean

PROBE_NOT_FOUND
#

lexer.ts view source

1073741823 import {PROBE_NOT_FOUND} from '@fuzdev/fuz_code/lexer.js';

What advance_probe returns when the text has no further occurrence: past the end of any string, since V8 caps string length below it. It is a small integer rather than Infinity so probe-cache fields, which start at -1, never change representation: a double stored there gives the cache a hidden class that only live caches hold, so a major GC between lex calls would discard every optimized lexer function that embeds it.

render_syntax_html
#

lexer.ts view source

(lexed: LexedSyntax): string import {render_syntax_html} from '@fuzdev/fuz_code/lexer.js';

Renders a lexed event stream to HTML in one forward pass. Gap text is copy-escaped; token spans use the precomputed open tags.

The result is built by appending, which in V8 makes a rope the first read copies into one string, at a cost per piece. So the loop appends few pieces and flat ones: an open tag is a single string rather than the tree + would have left at intern time, which every span would otherwise carry into the rope, and a one-char leaf, the most common kind in code, appends its whole span from TokenTypeInfo.char_spans. Text is sliced without a scan while the next character needing an escape, tracked by monotonic probes, lies past it.

lexed

returns

string

scan_balanced_braces
#

lexer.ts view source

(text: string, i: number, end: number): number import {scan_balanced_braces} from '@fuzdev/fuz_code/lexer.js';

Finds the matching } for the { at i, skipping js-style strings, templates, and comments. Returns -1 when unbalanced within the window.

text

type string

i

type number

end

type number

returns

number

scan_ident
#

lexer.ts view source

(text: string, from: number, end: number): number import {scan_ident} from '@fuzdev/fuz_code/lexer.js';

Scans an identifier starting at from (assumed to be an identifier start), returning the exclusive end index.

text

type string

from

type number

end

type number

returns

number

scan_to_line_end
#

lexer.ts view source

(text: string, i: number, end: number): number import {scan_to_line_end} from '@fuzdev/fuz_code/lexer.js';

Returns the index of the next \n at or after i (exclusive end of the line's content, excluding a preceding \r), or end when there is none. Uses native indexOf — the fast path for line-oriented scans.

text

type string

i

type number

end

type number

returns

number

skip_quoted
#

lexer.ts view source

(text: string, from: number, end: number, quote: number): number import {skip_quoted} from '@fuzdev/fuz_code/lexer.js';

Skips a js-style quoted span from the quote at from (used inside balanced scans), returning the index after the closing quote. Unterminated '/" strings stop at the newline; templates (`` `) span lines.

text

type string

from

type number

end

type number

quote

type number

returns

number

skip_space
#

lexer.ts view source

(text: string, from: number, end: number): number import {skip_space} from '@fuzdev/fuz_code/lexer.js';

Skips whitespace (including newlines) from from, returning the next non-space index.

text

type string

from

type number

end

type number

returns

number

syntax_events_to_tokens
#

SyntaxEventToken
#

lexer.ts view source

SyntaxEventToken import type {SyntaxEventToken} from '@fuzdev/fuz_code/lexer.js';

A flattened token span, in document order with containers before their children. Used by fixtures, tests, and range building.

type

type string

start

type number

end

type number

SyntaxLang
#

lexer.ts view source

SyntaxLang import type {SyntaxLang} from '@fuzdev/fuz_code/lexer.js';

A lexer-based language registration.

id

Primary language id, e.g. 'ts'.

type string

aliases?

Alternate ids resolving to this language, e.g. ['typescript'].

type string[]

lex

Lexes the lexer's current [pos, end) window, emitting token events. Must never throw and must always terminate with lexer.pos === lexer.end.

type (lexer: Lexer): void

lexer

type Lexer
returns void

lex_to_brace?

Lexes from lexer.pos as lex does, but stops at the first } that closes no { of the region's own, for a host whose region ends at that brace (a Svelte {expression}). Returns the brace's index, leaving lexer.pos there, or -1 after lexing to lexer.end without meeting one. The lexer finds the brace as it tokenizes, so a brace in a string, comment, or regex doesn't count. Optional: a host falls back to finding the brace itself.

With a stop_word, it also stops before that word where it stands alone outside every bracket of the region (the as of a Svelte {#each}), and returns the word's index instead; the host tells the two apart by the char there.

type (lexer: Lexer, stop_word?: string | undefined): number

lexer

type Lexer

stop_word?

type string
optional
returns number

token_type
#

lexer.ts view source

(name: string, alias?: string | string[] | undefined): number import {token_type} from '@fuzdev/fuz_code/lexer.js';

Interns a token type into token_types_global — the zero-config authoring path used by the built-in lexers' module-load type constants.

name

type string

alias?

type string | string[]
optional

returns

number

token_types_global
#

lexer.ts view source

TokenTypeRegistry import {token_types_global} from '@fuzdev/fuz_code/lexer.js';

The shared default registry — the single id space used by the built-in lexers' module-load token_type constants and by any SyntaxStyler not given its own registry. The type vocabulary is global by design, mirroring the global .token_* CSS namespace; per-registry isolation exists for fully-custom stylers and tests, whose lexers must intern into the same registry they're rendered against.

TokenTypeInfo
#

lexer.ts view source

TokenTypeInfo import type {TokenTypeInfo} from '@fuzdev/fuz_code/lexer.js';

Interned metadata for a token type.

id

type number

name

type string

aliases

type string[]

classes

Space-separated CSS classes, e.g. 'token_null token_keyword'.

type string

open_tag

Precomputed HTML open tag, e.g. '<span class="token_null token_keyword">'.

type string

char_spans

The whole rendered span of each one-char ASCII leaf of this type, indexed by char code, '' where not yet rendered. render_syntax_html fills it as it meets them; null until the first.

type string[] | null

TokenTypeRegistry
#

lexer.ts view source

import {TokenTypeRegistry} from '@fuzdev/fuz_code/lexer.js';

An id space of interned token types with precomputed CSS classes and HTML open tags. Ids are only meaningful against the registry that interned them — a lexed event stream resolves back through the registry stamped on its LexedSyntax.

infos

Interned infos indexed by id. Hot loops hoist and index this directly; grow it only via intern.

type TokenTypeInfo[]

readonly

intern

Interns a token type by name (+ optional aliases) and returns its id. Repeated calls with the same name and aliases return the same id. The CSS class list and HTML open tag are precomputed here so emitters never build class strings at runtime.

type (name: string, alias?: string | string[] | undefined): number

name

type string

alias?

type string | string[]
optional
returns number

info

Looks up the interned info for a token type id.

type (id: number): TokenTypeInfo

id

type number

trim_space_end
#

lexer.ts view source

(text: string, from: number, to: number): number import {trim_space_end} from '@fuzdev/fuz_code/lexer.js';

Trims trailing whitespace from a [from, to) span, returning the new exclusive end.

text

type string

from

type number

to

type number

returns

number

validate_syntax_events
#

lexer.ts view source

(lexed: LexedSyntax): string[] import {validate_syntax_events} from '@fuzdev/fuz_code/lexer.js';

Validates a lexed event stream's structural invariants, returning a list of human-readable issues (empty when valid): records well-formed, offsets monotonic and in-bounds, containers balanced.

lexed

returns

string[]

WordIndex
#

lexer.ts view source

import {WordIndex} from '@fuzdev/fuz_code/lexer.js';

A fixed table of ASCII words, each carrying an integer, that a span of text is looked up in without slicing it out. A lexer classifying every identifier through map.get(text.slice(start, end)) allocates and hashes a string per identifier; find compares char codes against the few words of the span's length and first char and returns the matching word's integer, so the lexer branches on a number. The integer is the lexer's to define: a kind, or a kind with flag bits beside it.

constructor

type new (words: Iterable<readonly [word: string, value: number]>): WordIndex

words

[word, value] entries, as a Map from words_map iterates. Each word must be non-empty ASCII and each value a non-zero 32-bit integer; a repeated word keeps its last value.

type Iterable<readonly [word: string, value: number], any, any>

find

Returns the value of the word text[start, end) spells, or 0 when it is none of the table's.

type (text: string, start: number, end: number): number

text

type string

start

type number

end

type number
returns number

words_map
#

lexer.ts view source

(...entries: [kind: number, words: string][]): Map<string, number> import {words_map} from '@fuzdev/fuz_code/lexer.js';

Builds a word→kind classification map from [kind, words] entries, where words is space-separated — the shared shape of the lexers' keyword tables.

entries

type [kind: number, words: string][]

returns

Map<string, number>

Imported by
#