Lily: a statically typed language whose reference implementation is an interpreter
Interpreted language focused on expressiveness and type safety.
At a glance
- What is it?
- Lily is a statically typed programming language written in C, delivered as an embeddable interpreter with sandboxing in mind, so the same engine can host several isolated instances and a program can be tried without a build step. It suits people embedding a scripting language in an application, and it is a small language project rather than an industry standard.
- Who is it for?
- Adopt Lily if you are embedding a scripting language inside a host application and want static typing and a sandboxed interpreter as a library you can link, because the reference implementation is exactly that and multiple interpreters can coexist. Do not adopt it if you need a broad ecosystem, a mature standard library or evidence of wide use, since the project is small, it has no published releases on GitHub and the package ecosystem lives in a separate repository.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly C, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 6, 2026, and from our analysis. They are not legal advice.
Star history
- FascinatedBox/lily1,096 ★ · +1
| Repository | 2026-10-20 | 2026-11-06 |
|---|---|---|
| FascinatedBox/lily | 1.1k | 1.1k |
1 Stars in the last 17 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
Static types, interpreted reference implementation
Lily is a programming language focused on expressiveness and type safety, and its homepage is lily-lang.org.
The interesting decision is not the type system, it is what the reference implementation is. Lily may be a statically typed language, but the reference implementation is an interpreter. The README frames that as a feature in two headings, and the two are the whole argument for the language.
The first is embeddability. The interpreter and its API have been carefully designed with sandboxing in mind, and as a result it is possible to have multiple interpreters exist alongside each other. The second is the shorter edit cycle. Another benefit of having the reference implementation as an interpreter is a shorter turnaround time, and the parser is comparable in speed to that of languages using an interpreter as their reference.
That combination is unusual. A statically typed language usually means a compiler, and a compiler usually means a build step before you can run anything. Lily gives you the type checking of a static language with the edit and run loop of an interpreted one, and the interpreter is a library rather than a monolithic binary, which is what makes the sandboxing claim possible in the first place.
The implementation is in C, the licence is MIT, and the last push to the repository was on 2026-10-11. The project is hosted on GitLab under the FascinatedBox organisation, with the repository on GitHub as a mirror.
What the sample program shows
The README opens with a sample, and it is worth reading closely because a small program says more about a language than a page of feature descriptions.
enum Color { Black, Blue, Cyan, Green, Magenta, Red, White, Yellow }
class Terminal(public var @foreground: Color, width_str: String)
{
public var @width = width_str.parse_i().unwrap_or(80)
public define set_fg(new_color: Color) {
@foreground = new_color
}
}
var terms = [Terminal(Color.White, "A"), Terminal(.Red, "40")]
terms.each(|e| e.width += 20 )
|> printSeveral things are visible. The enum is a plain list of names. The class declares an instance variable with the at sign, and the constructor takes a public variable and a plain parameter, with the parameter turned into a member by a separate public var declaration that gives it a default computed from the argument. That default calls parse_i on the string and unwraps it with a fallback of 80, so a malformed width does not crash the program.
Methods are introduced with define rather than the usual keyword, and the class exposes set_fg to change the foreground colour.
The instantiation shows two conveniences. The colour is given as the full Color.White the first time and as the bare .Red the second, and the type is inferred from the constructor parameter. The last two lines show iteration with a closure and a pipeline operator, where the result of each is passed into the next call.
The combination of a typed, explicit language with these conveniences is the expressiveness the project is aiming for.
Embeddable: an interpreter as a library, with sandboxing
The embeddability section is where the design decisions pay off, and it is the reason to look at this project at all if you are writing a host application.
The reference implementation is an interpreter, and the interpreter is a library. Building the project produces the lily executable and a liblily that you can use with your program, and there is a Findlily.cmake module in the repository for integrating it into a CMake based build.
The sandboxing claim is the substantive part. Because the interpreter and its API were designed with sandboxing in mind, multiple interpreters can exist alongside each other. That means a host can give each tenant, each plugin or each job its own interpreter, and one script cannot reach the state of another.
For a host application that evaluates user supplied scripts, this is the whole value proposition, and it is a differentiator from embedding a general purpose scripting language, where sandboxing is usually a matter of restricting what the host exposes rather than of the engine being built for the purpose.
The topics on the repository list embeddable and interpreter alongside language and scripting-languages, which is the same positioning stated as tags.
The trade is that Lily is a young language with a young standard library, so the work of exposing your own API to scripts is on you. The builtin module reference on the site is where to see what the language gives a script by default.
Building it: a C compiler and CMake
The build requirements are short, which is a good sign for a project written in C.
You need a C compiler supporting C11 or later, and CMake at 3.10 or later. There are no external dependencies, which is unusual for a language implementation and means the build does not pull a parser generator or a library stack into your system.
The build is two commands.
cmake .
cmake --build .Windows users may need to add the Unix Makefiles generator to the end of the cmake invocation.
What comes out is three things. The lily executable, which is the interpreter you run scripts with, the liblily library for embedding, and pre-commit-tests, the test runner.
The in-tree build is the one the README documents, rather than an out-of-tree build with a separate directory. For a project meant to be embedded, that is a reasonable choice for the reference build, though it does mean the build tree and the source tree are the same directory.
The continuous integration is set up on GitLab, with a pipeline badge and a coverage report, and there is an AppVeyor configuration for the Windows builds, which is the older of the two CI systems and shows the project's age.
The test suite: one file that pulls in the rest
The testing story is the part of the README that most rewards reading, because the design is unusual and clearly deliberate.
The centerpiece of the testing is a file called test_main.lily in the test directory, and that file imports and invokes a large number of tests that cover a lot of Lily. So the test suite is written in the language itself, and one entry point pulls the rest in.
Alongside it, the make command also builds covlib and pre-commit-tests. The README says no additional commands are necessary, and that covlib is a library that tests some parts of Lily that native code cannot test, while pre-commit-tests is a special runner that executes test_main.lily.
That split is the interesting part. Some behaviour can only be exercised from inside the language, and a library written in the language is how you reach it, while the parts that are testable from native code are presumably tested natively. The special runner exists because running a file in the language under test is not the same as running it with the ordinary interpreter.
To run the tests you execute pre-commit-tests from the directory it is in after building Lily. The name refers to a workflow rather than to a git hook, since the runner is invoked as a step before a commit is made, and the project uses a pre-commit hook as the trigger for running the suite.
The repository also carries a manifest directory, a run directory, a scripts directory and a try directory, which together hold the module manifest, the runtime support, the helper scripts and the try-it examples.
Distribution: the site, the browser sandbox and Garden
The resources listed in the README are worth noting because a language project usually has to solve three separate problems, and each of these solves a different one.
The documentation is at lily-lang.org, and the builtin module reference is a separate page, module.core.html, which is the reference for what the language provides out of the box. Those two solve learning the language.
The third resource is a sandbox you can try in your browser, at the intro-sandbox.html page, which solves the evaluation problem for a visitor who does not want to install a C toolchain to see whether the language is worth their time. Given that the language is interpreted, running it in a browser is a natural fit, and the same interpreter that a host application would link is what the sandbox is built on.
Packaging is solved by a separate repository. The lily-garden project contains a package manager called Garden that simplifies the install of Lily packages, so the language itself, the documentation and the package distribution are three separate pieces maintained in three separate places.
The repository also carries a RELEASES.md file, which is where the version history lives, since there are no GitHub releases for the project and the release notes are committed to the tree instead.
Editorial conclusion
Adopt Lily if you are embedding a scripting language inside a host application and want static typing and a sandboxed interpreter as a library you can link, because the reference implementation is exactly that and multiple interpreters can coexist. Do not adopt it if you need a broad ecosystem, a mature standard library or evidence of wide use, since the project is small, it has no published releases on GitHub and the package ecosystem lives in a separate repository. Before you build on it, read the builtin module reference and compile the project with CMake, because the language is young and its standard library is what you will be reading most.
Frequently asked questions
What is Lily?
Lily is a programming language focused on expressiveness and type safety. It is statically typed, but its reference implementation is an interpreter written in C, which the project presents as a feature for both embeddability and a shorter edit cycle.
How do I build Lily?
You need a C compiler with C11 support and CMake 3.10 or later, and there are no external dependencies. Run cmake . followed by cmake --build . in the repository, which produces the lily executable, the liblily library for embedding, and the pre-commit-tests runner.
How do I run the Lily test suite?
The centerpiece is test_main.lily in the test directory, which imports and invokes a large number of tests. The build also produces covlib, a library that tests parts of Lily that native code cannot, and pre-commit-tests, a runner that executes test_main.lily, which you run from the directory it is in.
Why is Lily written as an interpreter if it is statically typed?
The README gives two reasons. The interpreter and its API were designed with sandboxing in mind, so multiple interpreters can exist alongside each other, and having the reference implementation as an interpreter gives a shorter turnaround time, with a parser speed comparable to other interpreted languages.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/fascinatedbox-lily)Star history chart for your README
[](https://hysenlabs.com/projects/fascinatedbox-lily)Was this useful?