Nelumbo aims to be a powerful and extensible declarative logic programming language, designed for defining and executing custom syntax and semantics. As a meta-language, Nelumbo will be easily extensible, making it suitable for a wide range of applications. The goal is to integrate it with any IDE using the Language Server Protocol, allowing Nelumbo to serve as a language development platform. The language is currently developed in Java for seamless integration and performance. Please note that Nelumbo is in a very early stage of development, and incompatible changes are likely to occur.
- Features
- Documentation
- Building
- IDE Plugins
- Command-Line Interface
- HTTP Server
- MCP Server
- Examples
- Releasing
- Contributing
- License
- Support
- Define and parse syntaxes
- Define and execute semantics
- Purely declarative semantics
- Define and run tests
- Easily extensible
- Easily integrable
- IDE integration via LSP, including auto-formatting
- Written in Java
Detailed documentation.
Requires Java 21 or later.
Build everything (core library, LSP server, and all IDE plugins):
./gradlew buildBuild individual components:
./gradlew jar # core library
./gradlew :cli:cliJar # command-line runner + HTTP eval server (shaded jar)
./gradlew :lsp:server:serverJar # LSP server (shaded jar)
./gradlew :lsp:plugins:eclipse:jar # Eclipse plugin (includes LSP server)
./gradlew :lsp:plugins:intellij:build # IntelliJ pluginRun tests:
./gradlew testNelumbo has LSP-based editor plugins for multiple IDEs:
| IDE | Path | Details |
|---|---|---|
| Eclipse | lsp/plugins/eclipse |
Dropins-based plugin with semantic highlighting |
| IntelliJ | lsp/plugins/intellij |
IntelliJ platform plugin |
| VS Code | lsp/plugins/vscode |
VS Code extension |
| Neovim | lsp/plugins/neovim |
Neovim LSP configuration |
See the README in each plugin directory for installation instructions.
NelumboCli parses and evaluates one or more .nl files from the terminal, printing each query together with its inferred result. Queries that declare expected results are compared and mismatches are reported as errors. It is attached to every release as nelumbo-cli-<version>.jar, or build it with ./gradlew cliJar (output in cli/build/libs/nelumbo-cli-<version>.jar) and run it with java:
java -jar cli/build/libs/nelumbo-cli-<version>.jar [options] <file>...Pass - in place of a filename to read from stdin, or -n / --nelumbo followed by Nelumbo source to evaluate it directly from the command line. Use -p / --prep with a comma-separated list of stdlib modules (or all) to preload them before each evaluation and into a served knowledge base. Use -j / --json for machine-readable output: one JSON object with an errors message array, per-query facts/falsehoods name/value pairs, and the parse tree of the input (node kind, functor/type, position, text, children). Use -q / --quiet to suppress query output (errors are still reported) and -h / --help for the full option list. The process exits with 0 on success, 1 on parse/evaluation/comparison errors, and 2 on usage errors — suitable for scripting and CI.
The jar is also double-clickable: launched without a console and without arguments it opens an interactive window with an editable Nelumbo example, a Run button evaluating it in place, a server tab (start/stop the HTTP server with a request counter), and the command-line usage as documentation.
The same jar is a lean HTTP executor for .nl specifications: with --server <port> it loads the given files (directories are scanned for *.nl) and -n sources into a knowledge base and evaluates posted documents against it. It runs on the JDK's built-in HTTP server and has no third-party dependencies — the shaded jar is about 2 MB.
java -jar cli/build/libs/nelumbo-cli-<version>.jar --server 8080 [--timeout MS] [<file-or-dir>...]| Endpoint | Purpose |
|---|---|
POST /eval |
Evaluate a posted .nl document (raw text, or a JSON envelope {"document": "...", "limit": N}); returns query results and the parse tree as JSON |
POST /eval/trace |
Like /eval, with a (currently stubbed) trace field |
GET /metadata |
Knowledge base metadata: declared types, functors, rules, and facts |
GET /examples |
The bundled example names; GET /examples/<name> returns the source |
GET /health |
Liveness check |
GET / |
Info page with endpoint docs and a try-it form |
Each request is evaluated against a throwaway child of the loaded knowledge base, so requests are isolated from each other and never mutate shared state. The per-request inference budget defaults to 30 seconds (--timeout, 0 disables).
The website server (nelumbo-web-server-<version>.jar, the website module) serves the same REST endpoints plus an LSP editor service over WebSocket and the public pages of nelumbo.nl.
The mcp module is a Model Context Protocol stdio server that lets LLM agents author and verify self-contained .nl decision models. It exposes four tools:
| Tool | Purpose |
|---|---|
eval_nl |
Parse and evaluate a .nl model; returns structured diagnostics and per-query results, including whether embedded expected results matched |
search_docs |
Keyword search over the bundled language documentation |
get_example |
List or fetch the bundled working .nl examples |
new_model |
Return a commented, self-verifying skeleton model to start from |
Build the shaded jar:
./gradlew :mcp:mcpJarRegister it with an MCP client, for example Claude Code:
claude mcp add nelumbo -- java -jar $(pwd)/mcp/build/libs/nelumbo-mcp-server-<version>.jarThe server communicates over stdio (stdout is the JSON-RPC channel, logging goes to stderr). The query evaluation deadline defaults to 10 seconds and can be changed with --eval-deadline-ms <ms>.
The intended agent workflow: start from new_model, look up syntax with search_docs and get_example, then iterate with eval_nl until it reports ok=true with all expected query results matched. The bundled example clubFees.nl was authored end-to-end this way.
Person :: Object
Male :: Person
Female :: Person
FactType ::= pc(<Person>,<Person>) // parent-child
Person ::= p(<Person>), // parent
c(<Person>), // child
a(<Person>), // ancestor
d(<Person>), // descendant
m(<Person>), // mother
f(<Person>) // father
Person a, b, c
Male y
Female x
c(a)=b <=> pc(a,b)
p(a)=b <=> pc(b,a)
m(a)=b <=> E[x](c(x)=a & b=x)
f(a)=b <=> E[y](c(y)=a & b=y)
a(a)=b <=> d(b)=a
d(a)=c <=> c(a)=c |
E[b](d(a)=b & c(b)=c)
Male ::= Hendrik, Bernhard, Claus, Willem
Female ::= Wilhelmina, Juliana, Beatrix, Maxima, Amalia
pc(Hendrik, Juliana)
pc(Wilhelmina, Juliana)
pc(Juliana, Beatrix)
pc(Bernhard, Beatrix)
pc(Beatrix, Willem)
pc(Claus, Willem)
pc(Willem, Amalia)
pc(Maxima, Amalia)
a(Amalia)=a ? [(a=Beatrix),(a=Maxima),(a=Hendrik),(a=Bernhard),(a=Juliana),(a=Claus),(a=Willem),(a=Wilhelmina)][..]
m(Amalia)=Maxima ? [()][]
m(Amalia)=Willem ? [][()]
m(Amalia)=a ? [(a=Maxima)][..]
f(Amalia)=a ? [(a=Willem)][..]
f(m(f(Amalia)))=a ? [(a=Bernhard)][..]
Integer ::= fib(<Integer>)
Integer n, f
fib(n)=f <=> f=n if n<=1,
f=fib(n-1)+fib(n-2) if n>1
fib(0)=f ? [(f=0)][..]
fib(1)=f ? [(f=1)][..]
fib(5)=f ? [(f=5)][..]
fib(100)=f ? [(f=36#22r8fozas3n8w3)][..]
fib(1000)=f ? [(f=36#18nrvsuayughau0blk8aylvbyaqwiaqba77rdsgscn5hzwgbgaws8i8svp4xdmoo82plxiyogd5iaj1cspez8zfeio92a76t9n1frssxklr92wyyxm8r903o1ofgncikuggcwnf)][..]
Releases are created automatically by CI. The process works as follows:
- You edit
RELEASE_NOTES.mdin the repository root with the release notes for the upcoming version. You can use${version}and${version-num}for the version tag and version number respectively. - You merge your changes to
master, that's it! - The build workflow runs. On success, the Gradle
mvgTaggerplugin creates and pushes a version tag (e.g.v1.2.3). - The tag push triggers the release workflow, which downloads the build artifacts and creates a GitHub release with the
contents of
RELEASE_NOTES.md.
The release includes the following artifacts:
- Editor — standalone Swing-based editor (
nelumbo-ide-${version-num}.jar) - Eclipse plugin — dropins-based plugin (
nelumbo-eclipse-plugin-${version-num}.jar) - IntelliJ plugin — LSP4IJ-based plugin (
nelumbo-intellij-plugin-${version-num}.zip) - Slides — presentation slides (
nelumbo-slides-${version-num}.zip)
If RELEASE_NOTES.md is absent, GitHub will auto-generate release notes from the commit log.
Contributions and feedback are welcome! Please open issues or pull requests on GitHub.
This project is licensed under the terms of the LICENSE file provided in the repository.
For questions or support, please use the GitHub Issues page.