tree-sitter-concerto
March 19, 2026 · View on GitHub
A Tree-sitter grammar and parser for the Concerto Modelling Language by the Accord Project.
Concerto is a lightweight, object-oriented data modeling (schema) language designed for business concepts. It is used to define the structure of data in smart legal contracts, supply chain applications, and other business automation systems. Files use the .cto extension.
Features
- Complete grammar covering the full Concerto CTO language specification
- Language bindings for C, Node.js, Rust, Python, Go, and Swift
- 120 corpus tests all passing, plus 129 highlight assertions and 71 query validation tests
- CI pipeline via GitHub Actions (multi-platform parser tests, query validation, ts_query_ls lint, Rust and Go binding tests)
- Syntax highlighting queries for editor integration
- Text object queries for structural editing (compatible with nvim-treesitter-textobjects and Helix)
- Fold queries for code folding
- Locals queries for scope-aware features
- Indent queries for auto-indentation
- Cross-validated against the official
@accordproject/concerto-cliparser
Supported Language Constructs
| Construct | Status |
|---|---|
| Namespace declarations (versioned) | Supported |
| Imports (single, wildcard, multiple, aliased, with URI) | Supported |
| Concerto version statement | Supported |
| Concept declarations | Supported |
| Asset declarations | Supported |
| Participant declarations | Supported |
| Transaction declarations | Supported |
| Event declarations | Supported |
| Enum declarations | Supported |
| Scalar declarations (all primitive types) | Supported |
| Map declarations | Supported |
| All primitive types (String, Boolean, DateTime, Integer, Long, Double) | Supported |
| Object (reference) fields | Supported |
Relationship fields (-->) | Supported |
Array fields ([]) | Supported |
| Optional fields | Supported |
| Default values | Supported |
| Range validators | Supported |
| Regex validators | Supported |
| Length validators | Supported |
| Decorators (with all argument types) | Supported |
abstract modifier | Supported |
extends clause | Supported |
identified / identified by | Supported |
Line comments (//) | Supported |
Block comments (/* */) | Supported |
Quick Start
Prerequisites
- Node.js v16+
- tree-sitter CLI
Installation
# Clone the repository
git clone https://github.com/accordproject/concerto-tree-sitter.git
cd concerto-tree-sitter
# Install dependencies
npm install --ignore-scripts
# Generate the parser
tree-sitter generate
Usage
Parse a Concerto file
tree-sitter parse examples/basic.cto
Run the test suite
tree-sitter test
View syntax highlighting
tree-sitter highlight examples/basic.cto
Launch the interactive playground
npm run prestart # builds wasm first
npm start
Example
Here is a simple Concerto model:
namespace test@1.0.0
enum Country {
o UK
o USA
o FRANCE
}
concept Address {
o String street
o String city
o String postCode
o Country country
}
concept Person identified by name {
o String name
o Address address optional
@description("Height (cm)")
o Double height range=[0.0,]
o DateTime dateOfBirth
}
The parser produces a concrete syntax tree like:
(source_file
(namespace_declaration
(namespace_path))
(declaration_list
(enum_declaration
name: (type_identifier (identifier))
(enum_body
(enum_property name: (identifier))
(enum_property name: (identifier))
(enum_property name: (identifier))))
(concept_declaration
name: (type_identifier (identifier))
(class_body
(string_field name: (identifier))
(string_field name: (identifier))
(string_field name: (identifier))
(object_field
type: (type_identifier (identifier))
name: (identifier))))
(concept_declaration
name: (type_identifier (identifier))
(identified_by field: (identifier))
(class_body
(string_field name: (identifier))
(object_field
type: (type_identifier (identifier))
name: (identifier))
(double_field
(decorators
(decorator
name: (identifier)
(decorator_arguments
(decorator_arg_list
(decorator_string
(string_literal (string_content_double)))))))
name: (identifier)
(range_validator lower: (signed_real)))
(datetime_field name: (identifier))))))
Project Structure
concerto-tree-sitter/
grammar.js # The tree-sitter grammar definition (ESM)
tree-sitter.json # Tree-sitter configuration
package.json # Node.js package manifest
Cargo.toml # Rust crate manifest
go.mod # Go module manifest
pyproject.toml # Python package manifest
Package.swift # Swift package manifest
CMakeLists.txt # CMake build configuration
Makefile # C library build
binding.gyp # Node.js native addon build
.github/
workflows/
ci.yml # GitHub Actions CI pipeline
copilot-instructions.md # GitHub Copilot instructions
bindings/
c/ # C header and pkg-config template
node/ # Node.js native addon binding
rust/ # Rust crate binding
python/ # Python C extension binding
go/ # Go cgo binding
swift/ # Swift package binding
queries/
highlights.scm # Syntax highlighting queries
textobjects.scm # Text object queries (Neovim + Helix compatible)
locals.scm # Scope/definition/reference queries
indents.scm # Auto-indentation queries
folds.scm # Code folding queries
test/
corpus/ # Tree-sitter test corpus (120 tests)
highlight/ # Syntax highlighting assertion tests (129 assertions)
test-queries.sh # Query validation test script (71 tests)
examples/ # Example .cto files (validated with concerto-cli)
src/ # Generated C parser (auto-generated, do not edit)
Text Objects
The queries/textobjects.scm file provides structural text objects compatible with both nvim-treesitter-textobjects and Helix using a dual-capture naming pattern.
Each node carries both naming conventions (e.g. @class.outer @class.around). Each editor reads only the captures it recognises and ignores the rest.
| Concept | Neovim captures | Helix captures | Description |
|---|---|---|---|
| Class | @class.outer / @class.inner | @class.around / @class.inside | concept, asset, participant, transaction, event, enum, map, scalar |
| Block | @block.outer / @block.inner | — | class, enum, and map bodies (Neovim only) |
| Parameter | @parameter.inner | @parameter.inside | All field types, enum values, map key/value types |
| Assignment | @assignment.outer / @assignment.inner | — | Default value clauses (Neovim only) |
| Comment | @comment.outer | @comment.around / @comment.inside | Line and block comments |
Neovim (with nvim-treesitter-textobjects):
nvim-treesitter-textobjects does not provide default keymaps — you must configure them yourself. Below are example keymaps using the captures defined in textobjects.scm:
-- Select
vim.keymap.set({ "x", "o" }, "ac", function()
require("nvim-treesitter-textobjects.select").select_textobject("@class.outer", "textobjects")
end, { desc = "around class" })
vim.keymap.set({ "x", "o" }, "ic", function()
require("nvim-treesitter-textobjects.select").select_textobject("@class.inner", "textobjects")
end, { desc = "inside class" })
vim.keymap.set({ "x", "o" }, "ip", function()
require("nvim-treesitter-textobjects.select").select_textobject("@parameter.inner", "textobjects")
end, { desc = "inside parameter" })
-- Move (LazyVim configures ]c/[c and ]a/[a by default)
vim.keymap.set({ "n", "x", "o" }, "]c", function()
require("nvim-treesitter-textobjects.move").goto_next_start("@class.outer", "textobjects")
end, { desc = "Next class start" })
vim.keymap.set({ "n", "x", "o" }, "[c", function()
require("nvim-treesitter-textobjects.move").goto_previous_start("@class.outer", "textobjects")
end, { desc = "Prev class start" })
With those mappings, you can use motions like vic (select fields inside a declaration), vac (select entire declaration), dip (delete a field), and ]c / [c (jump between declarations).
Helix:
]t/[t— jump to next / previous declaration (type/class)mat/mit— select around / inside a declaration]a/[a— jump to next / previous parameter (field)]c/[c— jump to next / previous comment
Editor Integration
Neovim
Note: The parser is not yet in the nvim-treesitter registry, so
:TSInstall concertowill not work by default until you add the parser to the registry (for example via a customparser_config) or use one of the manual methods below.
Step 1: Register the .cto filetype
Add a plugin file (e.g., ~/.config/nvim/lua/plugins/concerto.lua for LazyVim, or anywhere in your config):
vim.filetype.add({
extension = {
cto = "concerto",
},
})
Step 2: Build and install the parser
git clone https://github.com/accordproject/concerto-tree-sitter.git
cd concerto-tree-sitter
tree-sitter generate
cc -shared -fPIC -o concerto.so src/parser.c -I src
Copy the compiled parser to Neovim's parser directory:
# macOS / Linux (default XDG data directory)
mkdir -p ~/.local/share/nvim/site/parser
cp concerto.so ~/.local/share/nvim/site/parser/concerto.so
# If you use a non-default XDG_DATA_HOME, you can instead run:
# mkdir -p "$XDG_DATA_HOME/nvim/site/parser"
# cp concerto.so "$XDG_DATA_HOME/nvim/site/parser/concerto.so"
Step 3: Install query files
Symlink (recommended for development) or copy the query files:
# Symlink from the cloned repo (edits to queries take effect immediately)
mkdir -p ~/.config/nvim/queries/concerto
ln -sf /path/to/concerto-tree-sitter/queries/highlights.scm ~/.config/nvim/queries/concerto/
ln -sf /path/to/concerto-tree-sitter/queries/locals.scm ~/.config/nvim/queries/concerto/
ln -sf /path/to/concerto-tree-sitter/queries/folds.scm ~/.config/nvim/queries/concerto/
ln -sf /path/to/concerto-tree-sitter/queries/indents.scm ~/.config/nvim/queries/concerto/
ln -sf /path/to/concerto-tree-sitter/queries/textobjects.scm ~/.config/nvim/queries/concerto/
Neovim auto-discovers parsers in ~/.local/share/nvim/site/parser/ and queries in ~/.config/nvim/queries/ — no additional plugin configuration is needed.
Alternative: nvim-treesitter parser_config (if using nvim-treesitter)
If you use nvim-treesitter and prefer it to manage the parser build, you can register it manually instead of steps 2–3 above:
local parser_config = require("nvim-treesitter.parsers").get_parser_configs()
parser_config.concerto = {
install_info = {
url = "https://github.com/accordproject/concerto-tree-sitter",
files = { "src/parser.c" },
branch = "main",
},
filetype = "concerto",
}
-- Then run :TSInstall concerto
Helix
Step 1: Add language and grammar config
Add to your ~/.config/helix/languages.toml:
[[language]]
name = "concerto"
scope = "source.concerto"
file-types = ["cto"]
comment-token = "//"
indent = { tab-width = 2, unit = " " }
[[grammar]]
name = "concerto"
source = { git = "https://github.com/accordproject/concerto-tree-sitter", rev = "main" }
Tip: For a pinned version, replace
rev = "main"with a specific commit SHA (e.g.,rev = "01ac8fd").
Step 2: Fetch and build the grammar
hx --grammar fetch
hx --grammar build
Step 3: Install query files
Important: Use the Helix-specific queries from
editor/helix/queries/concerto/, not the mainqueries/directory. The main queries use Neovim capture conventions which differ from Helix's.
mkdir -p ~/.config/helix/runtime/queries/concerto
cp editor/helix/queries/concerto/*.scm ~/.config/helix/runtime/queries/concerto/
Or symlink them for development (run from the repo root):
mkdir -p ~/.config/helix/runtime/queries/concerto
for f in editor/helix/queries/concerto/*.scm; do
ln -sf "$(pwd)/$f" ~/.config/helix/runtime/queries/concerto/
done
Verify with: hx --health concerto
Emacs (tree-sitter)
(add-to-list 'treesit-language-source-alist
'(concerto "https://github.com/accordproject/concerto-tree-sitter"))
;; Install with: M-x treesit-install-language-grammar RET concerto RET
Development
Grammar Design Decisions
The grammar is based on the official PEG grammar from the Concerto project, translated into tree-sitter's JavaScript DSL.
Key design choices:
-
Namespace/import paths as tokens: The dotted namespace path (e.g.,
org.accordproject.money@1.0.0) is captured as a single token to avoid ambiguity between the@in version tags and the@prefix for decorators. -
Distinct field nodes per type: Rather than a single generic "field" node, each primitive type gets its own node type (
string_field,integer_field,double_field, etc.). This enables precise syntax highlighting and type-specific validation (e.g., range validators only on numeric fields). -
Keyword word boundary: The grammar uses
word: $ => $._identifier_tokento ensure keywords likeconcept,enum,abstractare only recognized as keywords when they appear as complete words, not as prefixes of identifiers.
Validation
Example .cto files have been cross-validated against the official Concerto parser. To repeat this validation yourself, install the CLI separately (it is not a project dependency):
# Install the Concerto CLI globally (optional, for cross-validation only)
npm install -g @accordproject/concerto-cli
# Validate with the official Concerto CLI
concerto parse --model examples/basic.cto
# Parse with tree-sitter
tree-sitter parse examples/basic.cto
Running Tests
# Run all tests (corpus + highlights + query validation)
npm test
# Run only corpus and highlight tests
npm run test:corpus
# Run only query validation tests
npm run test:queries
Test Suite
The project has three layers of testing:
1. Corpus tests (120 tests in test/corpus/)
- Tree structure assertions for all language constructs
- Covers all declaration types, field types, imports, decorators, validators, comments
- Run via
tree-sitter test
2. Highlight tests (129 assertions in test/highlight/)
- Verifies syntax highlighting captures are correctly assigned
- Tests keyword, type, property, attribute, string, number, and punctuation highlighting
- Uses tree-sitter's built-in Sublime Text–style assertion format
- Run automatically as part of
tree-sitter test
3. Query validation tests (71 tests in test/test-queries.sh)
- Verifies all 5 query files compile and execute against all 6 examples without errors
- Asserts expected textobject captures (
@class.outer,@class.inner,@block.outer,@block.inner,@parameter.inner,@assignment.*,@comment.outer) are present - Asserts expected fold captures are present
- Asserts expected highlight captures across all major categories
- Run via
bash test/test-queries.sh
About Concerto
Concerto is maintained by the Accord Project, an open source, non-profit initiative working to transform contract management and contract automation by digitizing contracts. Accord Project operates under the umbrella of the Linux Foundation.
Resources
License
This project is licensed under the Apache License, Version 2.0. See LICENSE for the full text.
Copyright 2024-2026 Accord Project Contributors.