Sign in

    syntree

    Incremental syntax tree and highlighting toolkit for MoonBit

    syntax
    tree
    highlight
    moonbit
    Download zip
    Author
    Version
    0.2.1
    License
    MIT
    Last updated
    8 months ago
    Downloads
    81K

    No dependencies

    #syntree.mbt

    Incremental syntax tree and highlighting toolkit for MoonBit.

    #Packages

    • mizchi/syntree - core tree, cursor, highlight APIs, and extension mapping
    • mizchi/syntree/<lang> - language tokenizers + highlighters
    • mizchi/syntree/highlight - HTML rendering with inline styles
    • mizchi/syntree/syntree_api - JS exports for the bundled highlighters

    #Supported Languages (31)

    bash, c, cpp, csharp, css, dart, dockerfile, go, graphql, haskell, html, java, json, kotlin, lua, makefile, mdx, moonbit, ocaml, php, python, ruby, rust, scala, sql, swift, toml, typescript, xml, yaml, zig

    #MoonBit Usage

    #DCE-friendly selective imports

    Import only the languages you need for tree-shaking (Dead Code Elimination):

    import {
    "mizchi/syntree" // core + extension mapping
    "mizchi/syntree/typescript" // TypeScript/JavaScript only
    "mizchi/syntree/rust" // Rust only
    }

    fn main {
    // Get language from filename
    let lang = @syntree.get_language_for_filename("app.tsx") // Some("typescript")

    // Highlight based on detected language
    let source = "const x: number = 1"
    let html = match lang {
    Some("typescript") => @typescript.highlight_typescript_to_html(source)
    Some("rust") => @rust.highlight_rust_to_html(source)
    _ => source
    }
    println(html)
    }

    #Extension Mapping API

    // Get language name from file extension
    @syntree.get_language_for_ext(".ts") // Some("typescript")
    @syntree.get_language_for_ext(".rs") // Some("rust")
    @syntree.get_language_for_ext(".unknown") // None

    // Get language name from filename (handles special files)
    @syntree.get_language_for_filename("Dockerfile") // Some("dockerfile")
    @syntree.get_language_for_filename("Makefile") // Some("makefile")
    @syntree.get_language_for_filename("main.go") // Some("go")

    // List all supported languages
    @syntree.supported_languages() // ["bash", "c", "cpp", ...]

    // Get extensions for a language
    @syntree.get_extensions_for_language("typescript") // [".js", ".jsx", ".ts", ".tsx", ...]

    #Incremental Highlighting with LineCache

    LineCache provides line-based caching for efficient incremental syntax highlighting. It works with any language by accepting a highlight function:

    import { "mizchi/syntree", "mizchi/syntree/typescript" }

    fn main {
    let source = "const x = 1\nlet y = 2"

    // Create LineCache with a highlight function
    let cache = @syntree.LineCache::new(source, @typescript.highlight_typescript)

    // Get tokens for a specific line
    let line0_tokens = cache.get_line_tokens(0)

    // Get all tokens flattened
    let all_tokens = cache.all_tokens()

    // Update on edit (re-tokenizes from affected line)
    let new_source = "const x = 1\nlet y = 'hello'"
    let (start_line, end_line) = cache.update(new_source, 1, @typescript.highlight_typescript)

    // Full rebuild
    cache.rebuild("new source", @typescript.highlight_typescript)
    }

    LineCache API:

    MethodDescription
    LineCache::new(source, highlight_fn)Create cache from source
    line_count()Number of lines
    get_line_tokens(line)Tokens for a specific line
    all_tokens()All tokens flattened
    get_source()Current source text
    update(new_source, edit_line, highlight_fn)Incremental update, returns affected range
    rebuild(new_source, highlight_fn)Full rebuild

    #JS usage

    js/syntree_api.js wraps the MoonBit JS build output and exposes convenience helpers.

    import { highlight, highlightTypeScript } from "./js/syntree_api.js"; const html = highlight("const x = 1", "ts"); const html2 = highlightTypeScript("const x = 1");

    #Reference implementation

    • Lezer: https://lezer.codemirror.net/

    #Development

    just check just test just bench just info

    CursorFrame

    type CursorFrame

    Stack frame for tree traversal

    HighlightTag

    pub(all) enum HighlightTag {
    Keyword
    Operator
    Punctuation
    String
    Number
    Regexp
    Bool
    Null
    PropertyName
    VariableName
    FunctionName
    TypeName
    ClassName
    PrivateName
    Meta
    Bracket
    Brace
    Paren
    Comment
    DocComment
    TagName
    TagBracket
    Invalid
    None
    }

    Standard highlight tags for syntax highlighting
    impl Eq for HighlightTag

    HighlightTag::to_class

    fn HighlightTag::to_class(self : HighlightTag) -> String

    Convert HighlightTag to CSS class name

    HighlightToken

    pub(all) struct HighlightToken {
    from : Int
    to : Int
    tag : HighlightTag
    }

    A highlighted span of text

    HighlightToken::new

    fn HighlightToken::new(from : Int, to : Int, tag : HighlightTag) -> HighlightToken

    Create a highlight token

    Highlighter

    pub(all) struct Highlighter {
    rules : Map[String, HighlightTag]
    }

    Highlighter configuration - maps node types to highlight tags

    Highlighter::add_rule

    fn Highlighter::add_rule(self : Highlighter, node_name : String, tag : HighlightTag) -> Unit

    Add a highlighting rule

    Highlighter::get_tag

    fn Highlighter::get_tag(self : Highlighter, node_name : String) -> HighlightTag

    Get highlight tag for a node type

    Highlighter::highlight

    fn Highlighter::highlight(self : Highlighter, tree : Tree) -> Array[HighlightToken]

    Generate highlight tokens from a tree

    Highlighter::new

    Create a new highlighter

    LineCache

    pub(all) struct LineCache {
    lines : Array[Array[HighlightToken]]
    line_starts : Array[Int]
    source : String
    }

    Line-based cache for incremental syntax highlighting Works with any language by accepting a highlight function

    LineCache::all_tokens

    fn LineCache::all_tokens(self : LineCache) -> Array[HighlightToken]

    Get all tokens flattened

    LineCache::get_line_tokens

    fn LineCache::get_line_tokens(self : LineCache, line : Int) -> Array[HighlightToken]

    Get tokens for a specific line

    LineCache::get_source

    fn LineCache::get_source(self : LineCache) -> String

    Get source text

    LineCache::line_count

    fn LineCache::line_count(self : LineCache) -> Int

    Get number of lines

    LineCache::new

    fn LineCache::new(source : String, highlight_fn : (String) -> Array[HighlightToken]) -> LineCache

    Create a new line cache from source

    LineCache::rebuild

    fn LineCache::rebuild(self : LineCache, new_source : String, highlight_fn : (String) -> Array[HighlightToken]) -> Unit

    Rebuild the entire cache with new source

    LineCache::update

    fn LineCache::update(self : LineCache, new_source : String, edit_line : Int, highlight_fn : (String) -> Array[HighlightToken]) -> (Int, Int)

    Update cache when source changes Re-tokenizes from the edited line onwards Returns the range of lines that were affected (start_line, end_line)

    NodeType

    pub(all) struct NodeType {
    id : Int
    name : String
    is_error : Bool
    }

    Node type identifier with metadata
    impl Eq for NodeType
    impl Show for NodeType

    NodeType::error

    fn NodeType::error(id : Int) -> NodeType

    Create an error node type

    NodeType::new

    fn NodeType::new(id : Int, name : String) -> NodeType

    Create a new node type

    Tree

    pub(all) enum Tree {
    Node(NodeType, Int, Int, Array[Tree])
    Leaf(NodeType, Int, Int)
    Buffered(TreeBuffer, Int, Int)
    }

    • Buffer-backed subtrees for compact storage of many small nodes

    Tree::children

    fn Tree::children(self : Tree) -> Array[Tree]

    Get children (empty for Leaf and Buffered)

    Tree::from

    fn Tree::from(self : Tree) -> Int

    Get the start position of a tree

    Tree::iter

    fn Tree::iter(self : Tree) -> Iter[Tree]

    Iterate over all nodes in depth-first order

    Tree::leaf

    fn Tree::leaf(node_type : NodeType, from : Int, to : Int) -> Tree

    Create a leaf node

    Tree::length

    fn Tree::length(self : Tree) -> Int

    Get the length of the tree

    Tree::node

    fn Tree::node(node_type : NodeType, from : Int, to : Int, children : Array[Tree]) -> Tree

    Create a node with children

    Tree::node_type

    fn Tree::node_type(self : Tree) -> NodeType?

    Get the node type (returns None for Buffered trees)

    Tree::resolve

    fn Tree::resolve(self : Tree, pos : Int) -> Tree?

    Find the deepest node containing a position

    Tree::to

    fn Tree::to(self : Tree) -> Int

    Get the end position of a tree

    TreeBuffer

    pub(all) struct TreeBuffer {
    data : Array[Int]
    node_types : Array[NodeType]
    }

    This is inspired by compact tree buffer representations (Uint16Array style).

    TreeBuffer::get_node

    fn TreeBuffer::get_node(self : TreeBuffer, index : Int) -> (Int, Int, Int, Int)?

    Get node info at index

    TreeBuffer::new

    fn TreeBuffer::new(node_types : Array[NodeType]) -> TreeBuffer

    Create an empty TreeBuffer

    TreeBuffer::node_count

    fn TreeBuffer::node_count(self : TreeBuffer) -> Int

    Number of nodes in the buffer

    TreeBuffer::push_leaf

    fn TreeBuffer::push_leaf(self : TreeBuffer, type_id : Int, from : Int, to : Int) -> Unit

    Append a leaf node (no children)

    TreeCursor

    pub(all) struct TreeCursor {
    root : Tree
    // private fields
    }

    Allows walking the tree without allocating new node objects.

    TreeCursor::at_root

    fn TreeCursor::at_root(self : TreeCursor) -> Bool

    Check if cursor is at root

    TreeCursor::depth

    fn TreeCursor::depth(self : TreeCursor) -> Int

    Get depth in tree (0 = root)

    TreeCursor::first_child

    fn TreeCursor::first_child(self : TreeCursor) -> Bool

    Move to first child, returns false if no children

    TreeCursor::from

    fn TreeCursor::from(self : TreeCursor) -> Int

    Get current node's start position

    TreeCursor::name

    fn TreeCursor::name(self : TreeCursor) -> String

    Get current node's name

    TreeCursor::new

    fn TreeCursor::new(tree : Tree) -> TreeCursor

    Create a cursor at the root of a tree

    TreeCursor::next_sibling

    fn TreeCursor::next_sibling(self : TreeCursor) -> Bool

    Move to next sibling, returns false if no more siblings

    TreeCursor::node_type

    fn TreeCursor::node_type(self : TreeCursor) -> NodeType?

    Get current node's type

    TreeCursor::parent

    fn TreeCursor::parent(self : TreeCursor) -> Bool

    Move to parent, returns false if at root

    TreeCursor::reset

    fn TreeCursor::reset(self : TreeCursor) -> Unit

    Reset cursor to root

    TreeCursor::to

    fn TreeCursor::to(self : TreeCursor) -> Int

    Get current node's end position

    escape_html

    fn escape_html(s : String) -> String

    Escape HTML special characters

    escape_html_slice_to

    fn escape_html_slice_to(chars : Array[Char], from : Int, to : Int, buf : StringBuilder) -> Unit

    Escape HTML from char slice directly to StringBuilder (zero-alloc)

    get_extensions_for_language

    fn get_extensions_for_language(lang : String) -> Array[String]

    Get all extensions for a language name

    get_language_for_ext

    fn get_language_for_ext(ext : String) -> String?

    Get language name for a file extension (with leading dot) Returns None if the extension is not recognized

    get_language_for_filename

    fn get_language_for_filename(filename : String) -> String?

    Get language name for a filename (handles special files without extensions)

    supported_languages

    fn supported_languages() -> Array[String]

    List of all supported language names

    tokens_to_html

    fn tokens_to_html(source : String, tokens : Array[HighlightToken]) -> String

    Generate highlighted HTML from source and tokens Note: tokens are assumed to be already sorted by position (as produced by tokenizers)