https://github.com/healeycodes/quill
A Rust port of the Ink programming language (thesephist/ink)
https://github.com/healeycodes/quill
abstract-syntax-tree event-loop interpreter programming-language
Last synced: 9 months ago
JSON representation
A Rust port of the Ink programming language (thesephist/ink)
- Host: GitHub
- URL: https://github.com/healeycodes/quill
- Owner: healeycodes
- License: other
- Created: 2021-06-04T19:10:36.000Z (over 4 years ago)
- Default Branch: main
- Last Pushed: 2021-06-29T05:11:44.000Z (over 4 years ago)
- Last Synced: 2025-03-15T00:51:13.520Z (11 months ago)
- Topics: abstract-syntax-tree, event-loop, interpreter, programming-language
- Language: Rust
- Homepage:
- Size: 2.36 MB
- Stars: 7
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# ✒️ Quill
_This project is paused for now as I've got everything out of it that I wanted to (to learn some Rust, to learn the Ink source code). However, pull-requests/fixes are welcome and I will thoughtfully engage with them._
Quill is a tree-walk interpreter – it's an in-progress Rust port of [thesephist/ink](https://github.com/thesephist/ink) (a minimal programming language inspired by modern JavaScript and Go, with functional style). It uses Tokio async/await for an experimental event loop and passes callbacks via Tokio channels.
There are bugs, and the least-complete section is the runtime (e.g. the HTTP functionality, file read/write, etc.).
The variable/function naming, as well as the code design organization, tries to stick closely to the Go project. This way, patches that are added to Ink and be ported here. Many comments are lifted straight from the Go project (they start with `GoInk:`).
You may also be interested in [thesephist/schrift](https://github.com/thesephist/schrift) which is a more experimental runtime for Ink, focused on perf and instrumentation. Or the [The Ink blog](https://dotink.co/).
An example Ink program that Quill can interpret:
```ink
log := msg => out(string(msg) + char(10))
` vars `
a := 'Hello, World!'
log(a)
a := true
log(a)
` lists `
b := [1, 2, 3]
log(b)
` recursive via tail call optimization `
factorial := n => n :: {
0 -> 1
_ -> n * factorial(n-1)
}
factorial(5)
` event loop examples `
w := (t, c) => wait(t, () => log(c))
w(0.1, 'a')
w(0.2, 'b')
w(0.3, 'c')
w(0.35, 'd')
w(0.4, 'e')
w(0.45, 'f')
` this prints before the wait() calls `
log(string(time()))
` a map `
observation := {
weather: 'Sunny',
'observedAt': {
time: time()
}
}
log(observation.weather)
` composite value to_string `
log(observation.observedAt)
```
Outputs:
```
Hello, World!
true
{0: 1, 1: 2, 2: 3}
1624863950232.9634
Sunny
{time: 1624863950234.2705}
a
b
c
d
e
f
()
```
## Debug options
.. are currently forced on. So you'll get output like you were running Ink with `--verbose` and see debug information for the lexer, parser, and a frame dump.
e.g.
```
debug: lex -> identifier 'log' [41:1]
debug: lex -> '(' [41:4]
debug: lex -> identifier 'observation' [41:5]
debug: lex -> '.' [41:16]
debug: lex -> identifier 'observedAt' [41:17]
-- snip --
debug: parse -> Call (Identifier 'w') on (Number 0.45, String f)
debug: parse -> Call (Identifier 'log') on (Call (Identifier 'string') on (Call (Identifier 'time') on ()))
-- snip --
debug: frame dump {
a -> true
observation -> {observedAt: {time: 1624863950234.2705}, weather: 'Sunny'}
string -> Native Function (string)
w -> ier 'log') on (Identifier 'c'))))..
out -> Native Function (out)
factorial -> > (Binary (Identifier 'n') '*' (Call (Identifier 'factorial') on (Binary (Identifier 'n') '-' (Number 1))))})..
time -> Native Function (time)
char -> Native Function (char)
wait -> Native Function (wait)
b -> {0: 1, 1: 2, 2: 3}
log -> ' (Call (Identifier 'char') on (Number 10))))..
} -prnt-> *
```
## My motivations for this project
To learn:
- Rust (this is my first Rust project) [0]
- More about lexing, parsing, and evaluating an abstract syntax tree (AST)
- More about Ink's guts — how it's put together, where my Ink code comes alive, etc.
[0] this means the number of lines of code and the complexity of it is not in an ideal place. Call it a first draft.
## Things that are working
Lexing, parsing, and evaluation.
There is an experimental event loop which implements `wait()` to prove that it works. It uses Tokio async/await and channels for callbacks.
In terms of Ink's files, progress could also be measured like this:
- ink.go `5%`
- error.go `90%`
- eval.go `95%`
- lexer.go `100%`
- log.go `90%`
- parser.go `100%`
- runtime.go `2%`
## Current Bug
There are issues with defining (e.g. changing indexes of lists and keys of composite values).
:building_construction::building_construction::building_construction: