riju/doc/tutorial/run.md

4.8 KiB

Tutorial: provide run commands

Now that your language is installed you need to tell Riju how to run it. Here's an example for Dart:

main: "main.dart"
template: |
  void main() {
    print('Hello, world!');
  }  

run: |
  dart main.dart  

Note:

  • The contents of template are put into the main filename, and run is expected to run that file.
  • The main filename should follow existing conventions for your language, typically main.foo where foo is a standard file extension. If there's no standard file extension you can pick a reasonable-sounding one, like main.beatnik for Beatnik. You can use subdirectories (e.g. src/main.foo) if needed, but this is pretty rare.
  • The template code should print exactly the text Hello, world! with a trailing newline to stdout, or as close to that as possible.

Compiled languages

If your language has a separate compilation step that produces a binary or other intermediate artifact, you can add a separate compile command; for example:

main: "Main.java"
template: |
  public class Main {
      public static void main(String[] args) {
          System.out.println("Hello, world!");
      }
  }  

compile: |
  javac Main.java  
run: |
  java Main  

There is no hard requirement on the names of intermediate files. In the case of Java, the intermediate file is named Main.class, with the java command appending the .class part implicitly.

Languages with REPLs

If your language has the ability to run an interactive session, you can expose that functionality via the repl key. Here is the desired behavior for languages with REPLs:

  • The repl command will start an interactive session without any user code loaded
  • The run command will execute the user code in main, like before, and will then start an interactive session, preferably in the same environment (so variables are still in scope, for example)

Here is an example for Python (-u is to prevent output buffering):

repl: |
  python3 -u  

main: "main.py"
template: |
  print("Hello, world!")  

run: |
  python3 -u -i main.py  

Preserving variable scope

In the Python example above, passing -i main.py to python3 starts an interactive session where main.py is executed, and then any variables defined in main.py can be inspected at the REPL. This is the desired state of operation. Many interactive languages provide some command-line option(s) to achieve a similar effect, although they may be a bit obscure. For example, in Node.js, you have to pass the program as a string:

repl: |
  node  

main: "main.js"
template: |
  console.log("Hello, world!");  

run: |
  node -e "$(< main.js)" -i  

Or you may be able to abuse a "startup" or "rc" file that is loaded at startup in a special way. For example, Haskell's interpreter evaluates .ghci specially at startup:

repl: |
  rm -f .ghci
  ghci  

main: "Main.hs"
template: |
  module Main where

  main :: IO ()
  main = putStrLn "Hello, world!"  

run: |
  (echo ':load Main' && echo 'main') > .ghci && ghci  

In the case of shells, we often want to put the code itself into the startup file:

repl: |
  SHELL=/usr/bin/zsh HOME="$PWD" zsh  
input: |
  expr 123 \* 234  

main: ".zshrc"
template: |
  echo "Hello, world!"  
createEmpty: ""

run: |
  SHELL=/usr/bin/zsh HOME="$PWD" zsh  

In the above example, we passed createEmpty: "". To explain, the default behavior is for user code to be written into main immediately, even before the user has clicked Run. This is so that certain language servers will work correctly. However, in cases where we're abusing a startup file, it may not be appropriate. After all, zsh will load .zshrc no matter what (note that repl and run are identical here). By passing createEmpty: "", we make it so that Riju will write the provided string (i.e. "") into main before executing repl. This ensures that user code is not run when starting a REPL, but only when actually running.

Sometimes it's simply not possible to preserve variable scope from user code when starting an interactive session. In that case, we just start the interactive session separately. It's important to start the interactive session regardless of whether or not the user code had an error (so use ; instead of &&):

repl: |
  zoem  

main: "main.azm"
template: |
  \inform{Hello, world!}  

run: |
  zoem -I main.azm; zoem  

Required input

Sometimes languages really don't want to provide a way to run code noninteractively. In this case, you can include instructions about how the user can manually run the code:

repl: |
  hhvm -a
input: |
  print 123 * 234

main: "main.hack"
template: |
  <<__EntryPoint>>
  function main(): void {
    echo "Hello, world!\n";
  }

run: |
  echo "Type 'r' at the debugger prompt to run the code" && hhvm -a main.hack