Write a bunch of documentation
This commit is contained in:
parent
744e969b4a
commit
4d8c199a92
218
README.md
218
README.md
|
@ -91,8 +91,8 @@ you should submit a pull request :)
|
||||||
|
|
||||||
## Project setup
|
## Project setup
|
||||||
|
|
||||||
To run the webserver, all you need is Yarn. Just run `yarn install` as
|
To run the webserver, all you need is Yarn and LLVM. Just run `yarn
|
||||||
usual to install dependencies. For production, it's:
|
install` as usual to install dependencies. For production, it's:
|
||||||
|
|
||||||
$ yarn backend |- or run all three with 'yarn build'
|
$ yarn backend |- or run all three with 'yarn build'
|
||||||
$ yarn frontend |
|
$ yarn frontend |
|
||||||
|
@ -246,13 +246,217 @@ frequently.
|
||||||
scripting, but try to follow the standard installation procedure
|
scripting, but try to follow the standard installation procedure
|
||||||
where reasonable.
|
where reasonable.
|
||||||
|
|
||||||
### Running
|
### Language configuration
|
||||||
|
|
||||||
There are three categories of languages: non-interactive, interactive,
|
After installing the language, you'll need to configure it. This is
|
||||||
and interactive+scoped. The capabilities are as follows:
|
done by adding an entry to
|
||||||
|
[`backend/src/langs.ts`](backend/src/langs.ts).
|
||||||
|
|
||||||
* *Non-interactive:* You can run a file.
|
#### Required keys
|
||||||
* FIXME
|
|
||||||
|
Here is an example of a minimal language configuration, with only the
|
||||||
|
required keys:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
befunge: {
|
||||||
|
name: "Befunge",
|
||||||
|
main: "main.be",
|
||||||
|
run: "befunge-repl main.be",
|
||||||
|
template: `64+"!dlrow ,olleH">:#,_@
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
We have five things here:
|
||||||
|
|
||||||
|
* The language ID `befunge`, which appears in the URL
|
||||||
|
(<https://riju.codes/befunge>) and is used internally to track the
|
||||||
|
language. This can contain Unicode characters, but it must be safe
|
||||||
|
for URLs, so for example `+` is fine but `#` is not.
|
||||||
|
* The language display name `Befunge`, which is shown on the language
|
||||||
|
homepage and in some UI messages. The homepage is sorted by this
|
||||||
|
key.
|
||||||
|
* The name `main.be` of the file where a program is stored to be run.
|
||||||
|
When the user clicks the Run button, whatever is in the code editor
|
||||||
|
will be saved to this filename before the run command is executed.
|
||||||
|
We try to use a file extension that is actually appropriate for the
|
||||||
|
language, but if there's no standard one it's okay to pick something
|
||||||
|
that seems reasonable. The "Hello, world" resources on the [Riju
|
||||||
|
wiki](https://github.com/raxod502/riju/wiki) can be a helpful source
|
||||||
|
of plausible file extensions for obscure languages.
|
||||||
|
* The shell command `befunge-repl main.be` (executed in Bash) to run
|
||||||
|
the main file.
|
||||||
|
* The code `64+"!dlrow ,olleH">:#,_@` to prepopulate in the code
|
||||||
|
editor on page load. This should print "Hello, world!" exactly with
|
||||||
|
a trailing newline. Some languages are extremely difficult to write
|
||||||
|
in, so for those cases it's okay to copy a "Hello, world" program
|
||||||
|
from online even if it doesn't have the formatting quite right. The
|
||||||
|
`template` key should use a backtick-delimited string literal with a
|
||||||
|
trailing newline.
|
||||||
|
|
||||||
|
After you add these four keys, you should be able to test your
|
||||||
|
language at <http://localhost:6119/yourlanguage>. You shouldn't have
|
||||||
|
to manually restart anything if you're running `yarn dev` already.
|
||||||
|
|
||||||
|
#### Interactive languages
|
||||||
|
|
||||||
|
Some languages provide an interactive REPL facility. Such languages
|
||||||
|
should be configured to expose that functionality on Riju. That's done
|
||||||
|
by adding a `repl` key, like so:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
python: {
|
||||||
|
name: "Python",
|
||||||
|
repl: "python3 -u",
|
||||||
|
main: "main.py",
|
||||||
|
run: "python3 -u -i main.py",
|
||||||
|
template: `print("Hello, world!")
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
The `repl` key has another shell command to be executed with Bash,
|
||||||
|
which should launch a REPL independently of whatever may be in the
|
||||||
|
main file (in this case `main.py`). Also, for interactive languages,
|
||||||
|
the `run` command is different. It should not only run the code in
|
||||||
|
`main.py`, but then subsequently start the REPL. Many languages
|
||||||
|
provide a way to do this conveniently; for Python, the `-i` flag
|
||||||
|
forces the REPL to launch after `main.py` has finished executing.
|
||||||
|
|
||||||
|
One thing you will want to test is whether variables from your code
|
||||||
|
are accessible in the REPL. If it's possible to make this happen, do
|
||||||
|
it. Some languages require that you use a specific command-line
|
||||||
|
argument to get this behavior, while others may not support it at all.
|
||||||
|
|
||||||
|
For languages that do not have a convenient `-i` flag like Python, it
|
||||||
|
is okay to explicitly launch the REPL after running the code:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
kitten: {
|
||||||
|
name: "Kitten",
|
||||||
|
repl: "kitten",
|
||||||
|
main: "main.ktn",
|
||||||
|
run: "kitten main.ktn; kitten",
|
||||||
|
template: `"Hello, world!" say
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Hacking startup files
|
||||||
|
|
||||||
|
Many languages *appear* to have no way to start a REPL with your
|
||||||
|
code's variables in scope, but nevertheless have a secret backdoor.
|
||||||
|
Check to see if the language supports a "startup file" or "profile
|
||||||
|
file" or "REPL configuration file" or "rc file" or anything like that.
|
||||||
|
If it does, we can often put the user's code in there and it will be
|
||||||
|
read in a special way when the REPL starts up. This is especially
|
||||||
|
useful for shells:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
zsh: {
|
||||||
|
name: "Zsh",
|
||||||
|
repl: "SHELL=/usr/bin/zsh zsh",
|
||||||
|
main: ".zshrc",
|
||||||
|
createEmpty: ``,
|
||||||
|
run: `SHELL=/usr/bin/zsh zsh`,
|
||||||
|
template: `echo "Hello, world!"
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
When this hack is used, the `repl` and `run` commands are often the
|
||||||
|
same, with the different behavior of running the user's code or not
|
||||||
|
being caused by whether or not the code has been put into the profile
|
||||||
|
file (in this case `.zshrc`).
|
||||||
|
|
||||||
|
Note the use of the `createEmpty` key here. To make LSP work properly
|
||||||
|
(more on that later), the main file (here `.zshrc`) is actually
|
||||||
|
created even before the user clicks the Run button. This causes
|
||||||
|
problems for languages that use a startup file hack, since when
|
||||||
|
executing the `repl` shell command, the "Hello, world" code from the
|
||||||
|
template will get executed, which is undesired. To work around this,
|
||||||
|
the `createEmpty` key allows you to provide a special value to write
|
||||||
|
into the main file (here `.zshrc`) before executing the `repl` shell
|
||||||
|
command. Providing the empty string ensures no user code gets executed
|
||||||
|
when we just want to launch a REPL.
|
||||||
|
|
||||||
|
Check out Beanshell, Elvish, Factor, GEL, Ksh, R, SageMath, Sh, Tcl,
|
||||||
|
Tcsh, and Zsh for examples of this hack.
|
||||||
|
|
||||||
|
#### Compiled languages
|
||||||
|
|
||||||
|
For languages that have a compilation step, it's nice to split out
|
||||||
|
that step into a separate shell command under the `compile` key, like
|
||||||
|
so:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
c: {
|
||||||
|
name: "C",
|
||||||
|
main: "main.c",
|
||||||
|
compile: "clang -Wall -Wextra main.c -o main",
|
||||||
|
run: "./main",
|
||||||
|
template: `#include <stdio.h>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
printf("Hello, world!\\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
It's not treated any differently by Riju at present than just cramming
|
||||||
|
both steps into the `run` key with a `&&`, but we could implement
|
||||||
|
optimizations later such as only re-running the compile step if the
|
||||||
|
code actually changed.
|
||||||
|
|
||||||
|
#### Integration tests
|
||||||
|
|
||||||
|
Riju has a suite of over 500 integration tests covering all supported
|
||||||
|
languages. This is to ensure that our aggressive rolling-release
|
||||||
|
policy does not lead to breakage.
|
||||||
|
|
||||||
|
My design philosophy of tests is that if they require any effort to
|
||||||
|
write, nobody is going to write them, least of all me. For this
|
||||||
|
reason, Riju uses a
|
||||||
|
[convention-over-configuration](https://en.wikipedia.org/wiki/Convention_over_configuration)
|
||||||
|
scheme to automatically synthesize the majority of the integration
|
||||||
|
tests without the need for any code to be written.
|
||||||
|
|
||||||
|
There are seven possible tests each language can have, and each
|
||||||
|
language has some subset of them:
|
||||||
|
|
||||||
|
* `run`: Verify that running the language with the default code causes
|
||||||
|
`Hello, world!` to be printed.
|
||||||
|
* `repl`: Verify that typing in an expression at the REPL (by default
|
||||||
|
`123 * 234`) causes a result to be output (by default `28782`).
|
||||||
|
* `runrepl`: Same as `repl`, but after the Run button is clicked. This
|
||||||
|
proves that a REPL is being started after the code finishes running.
|
||||||
|
* `scope`: Verify that a variable defined in the code is accessible in
|
||||||
|
the REPL after the code is run.
|
||||||
|
* `format`: Verify that a code formatter correctly reformats a piece
|
||||||
|
of sample code.
|
||||||
|
* `lsp`: Verify that an LSP server produces a particular completion.
|
||||||
|
* `ensure`: Run an arbitrary shell command and verify that it exits
|
||||||
|
successfully. This test is currently not used by any language.
|
||||||
|
|
||||||
|
The `run`, `repl`, and `runrepl` tests are configured automatically
|
||||||
|
with default values for every language. The other test types are not
|
||||||
|
configured automatically, because there is no way to pick a reasonable
|
||||||
|
default for the behavior they are testing. Use the following list to
|
||||||
|
identify which tests you should make sure are configured for your
|
||||||
|
language:
|
||||||
|
|
||||||
|
* `run`: all languages
|
||||||
|
* `repl`, `runrepl`: interactive languages (ones with a `repl` key)
|
||||||
|
* `scope`: interactive languages where you can access code variables
|
||||||
|
from the REPL
|
||||||
|
* `format`: languages with code formatters (see below)
|
||||||
|
* `lsp`: languages with LSP support (see below)
|
||||||
|
|
||||||
|
##### Test configuration
|
||||||
|
|
||||||
|
FIXME
|
||||||
|
|
||||||
## Debugging tools
|
## Debugging tools
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue