tree-sitter-dune
March 20, 2026 · View on GitHub
Tree-sitter grammar for Dune build system files.
Features
- Complete dune file support: Parses all major stanzas including
executable,library,rule,test,alias, and more - dune-project support: Full support for project metadata files including
lang,name,version,package, and all project configuration stanzas - dune-workspace support: Complete support for workspace configuration including
context,profile, and cross-compilation setup - Syntax highlighting: Comprehensive highlight queries for editor integration
- Field markers: Structured AST with named fields for precise code navigation
- 86+ test cases: Extensively tested against real-world Dune files
Supported File Types
dune- Build files in each directorydune-project- Project metadata and configurationdune-workspace- Workspace-level settings and build contexts
Installation
NPM
npm install tree-sitter-dune
Cargo
cargo add tree-sitter-dune
Tree-sitter CLI
git clone https://github.com/tmcgilchrist/tree-sitter-dune
cd tree-sitter-dune
tree-sitter generate
tree-sitter test
Usage
Node.js
const Parser = require('tree-sitter');
const Dune = require('tree-sitter-dune');
const parser = new Parser();
parser.setLanguage(Dune);
const sourceCode = `
(library
(name my_lib)
(libraries base stdio))
`;
const tree = parser.parse(sourceCode);
console.log(tree.rootNode.toString());
Rust
use tree_sitter::Parser;
fn main() {
let mut parser = Parser::new();
parser.set_language(&tree_sitter_dune::LANGUAGE.into()).expect("Error loading Dune grammar");
let source_code = r#"
(library
(name my_lib)
(libraries base stdio))
"#;
let tree = parser.parse(source_code, None).unwrap();
println!("{}", tree.root_node().to_sexp());
}
Python
from tree_sitter import Language, Parser
import tree_sitter_dune
parser = Parser()
parser.set_language(Language(tree_sitter_dune.language(), "dune"))
source_code = b"""
(library
(name my_lib)
(libraries base stdio))
"""
tree = parser.parse(source_code)
print(tree.root_node.sexp())
Go
package main
import (
"context"
"fmt"
sitter "github.com/tree-sitter/go-tree-sitter"
dune "github.com/tmcgilchrist/tree-sitter-dune/bindings/go"
)
func main() {
parser := sitter.NewParser()
parser.SetLanguage(sitter.NewLanguage(dune.Language()))
sourceCode := []byte(`
(library
(name my_lib)
(libraries base stdio))
`)
tree := parser.Parse(sourceCode, nil)
fmt.Println(tree.RootNode().ToSexp())
}
Editor Integration
Neovim
With nvim-treesitter:
require'nvim-treesitter.configs'.setup {
ensure_installed = { "dune" },
highlight = {
enable = true,
},
}
Emacs
neocaml provides neocaml-dune-mode, a tree-sitter based major mode for dune, dune-project, and dune-workspace files. It uses Emacs's built-in treesit library (Emacs 29+).
Grammar Coverage
dune files
✅ Fully Supported:
executable,executables- Build executableslibrary- OCaml librariestest,tests- Test executablesrule- Custom build rules with actionsalias- Named build targetsinstall- Installation rulestoplevel- Custom toplevelscopy_files- File copying with globsinclude- Include dune fragmentsmenhir- Menhir parser generatorocamllex,ocamlyacc- Lexer/parser generationforeign_library- C/C++ librariesplugin- Compiler pluginsmdx- Markdown executable blockscoq.theory- Coq integrationcram- Cram testssubdir- Nested directoriesenv- Environment configurationignored_subdirs- Ignore directoriesdeprecated_library_name- Library renaming- Actions:
run,bash,copy,diff,write-file,with-stdout-to,progn, and 15+ more
Coverage: 30/34 stanzas (88%), ~99% real-world usage
dune-project files
✅ Fully Supported (100% coverage):
lang- Language version (required)name,version,license- Project metadataauthors,maintainers,maintenance_intent- Contributorshomepage,documentation,bug_reports- URLssource- Repository information (github/git/uri)package- Package definitions with complex dependenciesgenerate_opam_files,opam_file_location- OPAM integrationusing- Extension configuration (menhir, coq, etc.)formatting- Formatter settingsdialect- Custom dialectspin- Pin dependencieswarnings- Compiler warnings configexplicit_js_mode- JavaScript modesubst- Watermarking- Boolean flags:
accept_alternative_dune_file_name,executables_implicit_empty_intf,expand_aliases_in_sandbox,implicit_transitive_deps,map_workspace_root,use_standard_c_and_cxx_flags,wrapped_executables
Coverage: 29/29 stanzas (100%)
dune-workspace files
✅ Fully Supported:
context- Build contexts(context default)- Default context(context (opam ...))- Opam switch contexts- Fields:
switch,name,profile,toolchain,host,target - Flags:
merlin,generate_merlin_rules,disable_dynamically_linked_foreign_archives - Additional:
root,lock_dir,paths,env,fdo,targets
profile- Build profile selection (dev/release/custom)
Development
Building
npm install -g tree-sitter-cli
tree-sitter generate
Testing
tree-sitter test
Debugging
tree-sitter parse examples/dune
tree-sitter parse --debug examples/dune-project
Contributing
Contributions are welcome! Please:
- Add tests for any new features in
test/corpus/ - Ensure all tests pass with
tree-sitter test - Update this README if adding new stanza support
- Follow the existing code style
Adding New Stanzas
-
Define the stanza in
grammar.js:_stanza_my_new_stanza: ($) => dune_stanza( $, "my_stanza", choice( dune_field($, "field_name", $.value_type), $.sexp, ), ), -
Add it to the
stanzachoice list -
Create tests in
test/corpus/ -
Run
tree-sitter generate && tree-sitter test
Resources
License
MIT