Skip to content
Permalink
Browse files

Update spec, readme, and todos

  • Loading branch information...
thesephist committed Aug 4, 2019
1 parent fc29c33 commit 63902edeba0c021f7ec839339c8c4c324bd19dcc
Showing with 15 additions and 21 deletions.
  1. +4 −4 README.md
  2. +1 −1 SPEC.md
  3. +10 −16 TODO.md
@@ -74,7 +74,7 @@ If you're looking for more realistic and complex examples, check out...
- [a small static file server](samples/fileserver.ink)
- [Mandelbrot set renderer](samples/mandelbrot.ink)

You'll notice a few characteristic things about Ink:
You'll notice a few characteristics about Ink:

- Functions are defined using arrows (`=>`) _a la_ JavaScript arrow functions
- Ink does not have a looping primitive (no `for` or `while`), and instead defaults to tail-optimized recursion. Loops may be possible to have in syntax with macros in the near future.
@@ -123,9 +123,9 @@ I'm also very interested in Elixir's approach towards language development, wher

Ink has a very small surface area to interface with the rest of the interpreter and runtime, which is through the list of builtin functions defined in `runtime.go`. In an effort to make it safe and easy to run potentially untrusted scripts, the Ink interpreter provides a few flags that determine whether the running Ink program may interface with the operating system in certain ways. Rather than simply fail or error on any restricted interface calls, the runtime will silently ignore the requested action and potentially return empty but valid data.

- `--no-read`: When enabled, the builtin `read()` function will simply return an empty read, as if the file being read was of size 0. `--no-read` also blocks directory traversals.
- `--no-write`: When enabled, the builtins `write()`, `delete()`, and `make()` will pretend to have written the requested data or finished the requested filesystem operations safely, but cause no change.
- `--no-net`: When enabled, the builtin `listen()` function will pretend to have bound to a local network socket, but will not actually bind. The builtin `req()` will also pretend to have sent a valid request, but will do nothing.
- `-no-read`: When enabled, the builtin `read()` function will simply return an empty read, as if the file being read was of size 0. `-no-read` also blocks directory traversals.
- `-no-write`: When enabled, the builtins `write()`, `delete()`, and `make()` will pretend to have written the requested data or finished the requested filesystem operations safely, but cause no change.
- `-no-net`: When enabled, the builtin `listen()` function will pretend to have bound to a local network socket, but will not actually bind. The builtin `req()` will also pretend to have sent a valid request, but will do nothing.

To run an Ink program completely untrusted, run `ink -isolate` (with the "isolate" flag), which will revoke all revokable permissions from the running script.

@@ -135,7 +135,7 @@ A single process of Ink program first executes its entrypoint programs, and then

### Concurrent processes

This is behind rationale that a program is fundamentally a representation of a single system evolving sequentially, and shared state means two threads are actually a single program, which breeds all sorts of complexity when a single system tries to mutate in two different sequences. Rust's solution is innovative (compile time static checking that shared mutation never occurs), but a more minimal and Inky way dealing with this is to not have shared state, and only communicate by passing serialized data (messages) between threads of execution that are otherwise spawned and execute in isolation. This is in essence JavaScript workers, but where messages can be any serialized data.
An Ink program is fundamentally single threaded. In the interpreter, this is enforced by a mutex that acts as an execution lock. This is behind rationale that a program is fundamentally a representation of a single system evolving sequentially, and shared state means two threads are actually a single program, which breeds all sorts of complexity when a single system tries to mutate in two different sequences. Rust's solution is innovative (compile time static checking that shared mutation never occurs), but a more minimal and Inky way dealing with this is to not have shared state, and only communicate by passing serialized data (messages) between threads of execution that are otherwise spawned and execute in isolation. This is in essence JavaScript workers, but where messages can be any serialized data.

Ink implements this with three builtin functions, `receive(processID, handler) => null` and `send(processID, message) => null` for sending and receiving messages, and `create(function) => processID` for spawning threads. ProcessID (pid) is an opaque handle passed around but it's a standard Ink value/type (most likely an integer). Once a function has been spawned off into a separate process, it can choose to listen and receive message. Send will _not block_ even if nothing is listening (nothing in Ink does unless explicitly documented / chosen). The handler will receive the message as its only argument, where the message may be any valid Ink value, including `()` (the null value).

26 TODO.md
@@ -3,36 +3,30 @@

## Interpreter

- [ ] Set up Travis CI for Ink: for now, just run `make run && make test`, and pass/fail on the exit value.
- [ ] Potential room for optimizations:
- Reducing memory allocations. Specifically, pooling `StackFrame` and other function call-related data structures to reduce allocations.
- Optimized tail recursion unwrapping that doesn't require cost of wrapping and then immediatley unwrapping thunks.
- Cache locality for data structures (stack-local variables?)
- [ ] Implement go-fuzz to fuzz test the whole toolchain
- go-fuzz talk: http://go-talks.appspot.com/github.com/dvyukov/go-fuzz/slides/go-fuzz.slide#1
- [ ] Implement the concurrency system (`send()`, `receive()`, `create()` builtins) as described in the language spec.
- [ ] Make the interpreter a Homebrew brew tap
- [ ] Ink -> JavaScript in JS and/or an Ink interpreter in JS and maybe ship it as a javascript compiler? Great for
1. correctness checking against Go implementation
2. writing web code
3. having a second independent implementation of the language
- If we have ink in go and JS we can fuzz both together which allows us to also test for correctness better
- [ ] Implement go-fuzz to fuzz test the whole toolchain
- go-fuzz talk: http://go-talks.appspot.com/github.com/dvyukov/go-fuzz/slides/go-fuzz.slide#1
- [ ] Set up travis ci for Ink, and for now make it run run-all and then the test-all script. If zero exit value, it worked.
- [ ] --no-color option for piping output to another application / for scripting use (e.g. inker).
- [ ] Potential room for optimizations:
- Reducing memory allocations. Specifically, pooling `StackFrame` and other function call-related data structures to reduce allocations.
- Optimized tail recursion unwrapping that doesn't require cost of wrapping and then immediatley unwrapping thunks.
- Cache locality for data structures (stack-local variables?)
- [ ] Make the interpreter a Homebrew brew tap


## Language
## Language + standard library

- [ ] A procedural macro system to extend the language syntax. Build a token stream-aware macro system into the standard library.
- [ ] Type system? I like the way typescript does it, I think ink’s type checker should be a completely separate layer in the toolchain from the lex/parse/eval layer. But let’s think about the merits of having type annotations and how we can make it simple to lex/parse out while effective at bringing out the forte’s of ink’s functional style.
- It seems helpful to think of it as a constraint system on the runtime, instead of as something that’s an attribute of the runtime execution itself. I like the Haskell approach of having definition / type declarations separate from the imperative flow definition/syntax itself. Also look at how Haskell / Erlang / Clojure / other functional languages do type annotation, and keep in mind that while Ink is functional flavor it should still feel great in imperative / OO style.
- Since Ink has no implicit casts, this seems like it'll be straightforward to infer most variable types from their declaration (what they're bound to in the beginning) and recurse up the tree. So to compute "what's the type of this expression?" the type checker will recursively ask its children for their types, and assuming none of them return an error, we can define a `func (n Node) TypeCheck() (Type, error)` that recursively descends to type check an entire AST node from the top. We can expose this behind an `ink -type-check <file>.ink` flag.
- To support Ink's way of error handling and signaling (returning null data values), the type system must support sum types, i.e. `number | ()`


## Standard library / utilities

- [ ] Promises / futures should be in the standard library in Ink, composed of callback primitives.
- [ ] We should study event systems / event loop models like libuv and Tokio more, especially in light of Golang's strange Erlangy processes / green threads model.
- [ ] Should study event systems / event loop models like libuv and Tokio more, especially in light of Golang's strange Erlangy processes / green threads model.


## Potential examples / projects

0 comments on commit 63902ed

Please sign in to comment.
You can’t perform that action at this time.