gluon_repl 0.8.0

REPL for gluon. A static, type inferred programming language for application embedding
let prelude = import! std.prelude
let io @ { ? } = import! std.io
let map @ { Map } = import! std.map
let { Bool } = import! std.bool
let { Option } = import! std.option
let { Result } = import! std.result
let string = import! std.string
let thread = import! std.thread
let array = import! std.array
let { ref, load, (<-) } = import! std.reference
let rustyline = import! rustyline
let { ReadlineError } = import! rustyline_types
let repl_prim = import! repl.prim
let { (<<) } = import! std.function

let { Applicative, wrap, (*>) } = import! std.applicative
let { flat_map, (>>=) } = import! std.monad

let { append = (++) } = string.semigroup
let ord_map @ { singleton, find, insert, ? } = map.make string.ord
let { (<>) } = import! std.prelude
let { empty } = ord_map.monoid


let run_interruptible_io cpu_pool action : CpuPool -> IO String -> IO (Result String String) =
    do eval_thread = thread.new_thread ()
    let interruptible_action = repl_prim.finish_or_interrupt cpu_pool eval_thread action
    io.catch (io.functor.map Ok interruptible_action) (wrap << Err)


let load_file cpu_pool filename : CpuPool -> String -> IO String =
    let last_slash =
        match string.rfind filename "/" with
        | None -> 0
        | Some i -> i + 1
    let modulename = string.slice filename last_slash (string.len filename - 3)
    let action =
        do expr = io.read_file_to_string filename
        do result = io.load_script modulename expr
        wrap result

    do result = run_interruptible_io cpu_pool action
    match result with
    | Ok x -> wrap x
    | Err x -> wrap x

let run_file cpu_pool filename : CpuPool -> String -> IO () =
    let action =
        do expr = io.read_file_to_string filename
        do result = io.run_expr expr
        wrap (result.value ++ " : " ++ result.typ)

    do result = run_interruptible_io cpu_pool action
    match result with
    | Ok _ -> io.println ""
    | Err x -> io.println x

type ReplAction = | Continue | Quit
type Cmd = { name : String, alias : String, info : String, action : String -> IO ReplAction }
type Commands = Map String Cmd

let make_commands cpu_pool : CpuPool -> Commands =
    let print_result result =
        match result with
        | Ok x -> io.println x
        | Err x -> io.println x

    let commands = ref []
    let cmds : Array Cmd = [{
            name = "quit",
            alias = "q",
            info = "Quit the REPL",
            // FIXME a -> IO ReplAction signature should not be necessary
            action =
                let x : a -> IO ReplAction = \_ -> wrap Quit
                x,
        }, {
            name = "type",
            alias = "t",
            info = "Prints the type with an expression",
            action =
                \arg ->
                repl_prim.type_of_expr arg >>= print_result
                    *> wrap Continue,
        },
        {
            name = "info",
            alias = "i",
            info = "Prints information about the given name",
            action = \arg -> repl_prim.find_info arg >>= print_result *> wrap Continue,
        },
        {
            name = "kind",
            alias = "k",
            info = "Prints the kind with the given type",
            action = \arg -> repl_prim.find_kind arg >>= print_result *> wrap Continue,
        },
        {
            name = "load",
            alias = "l",
            info = "Loads the file at \'folder/module.ext\' and stores it at \'module\'",
            action = \arg -> load_file cpu_pool arg >>= io.println *> wrap Continue,
        },
        {
            name = "script",
            alias = "s",
            info = "Runs the script at `FILENAME`",
            action = \arg -> run_file cpu_pool arg *> wrap Continue,
        },
        {
            name = "help",
            alias = "h",
            info = "Print this help",
            action = \_ ->
                let print_header = io.println "Available commands\n"
                let print_cmd cmd : Cmd -> IO () =
                    io.println ("    :" ++ cmd.name ++ " (" ++ cmd.alias ++ ") " ++ cmd.info)

                print_header *> array.traversable.traverse io.applicative print_cmd (load commands)
                    *> wrap Continue,
        }]
    commands <- cmds
    array.foldable.foldl
        (\map cmd -> singleton cmd.name cmd <> singleton cmd.alias cmd <> map)
        empty
        cmds

let { Parser, parse, ? } = import! std.parser


let cmd_parser : Parser { cmd : String, arg : String } =
    let {
        any,
        recognize,
        skip_many1,
        token,
        spaces,
        letter,
        monad = { flat_map },
        alternative,
        applicative } = import! std.parser
    let { (<|>) } = import! std.applicative

    let word = recognize (skip_many1 letter)
    let arg_parser = recognize (skip_many1 any)

    do _ = token ':'
    do cmd = word
    do arg = (spaces *> arg_parser) <|> wrap ""
    wrap { cmd, arg }

let do_command commands line : Commands -> String -> IO ReplAction =
    match parse cmd_parser line with
    | Ok { cmd, arg } ->
        match find cmd commands with
        | Some command -> command.action arg
        | None -> io.println ("Unknown command \'" ++ cmd ++ "\'") *> wrap Continue
    | Err err ->
        io.println "Expected a command such as `:h`"
            *> wrap Continue

let store line : String -> IO ReplAction =
    let line = string.trim line
    match string.find line " " with
    | Some bind_end ->
        let binding = string.slice line 0 bind_end
        let expr = string.slice line bind_end (string.len line)
        io.load_script binding expr *> wrap Continue
    | None -> io.println "Expected binding in definition" *> wrap Continue

type Repl = { commands : Commands, editor : Editor, cpu_pool : CpuPool }

let loop repl : Repl -> IO () =
    let run_line line =
        if string.is_empty (string.trim line) then
            wrap Continue
        else if string.starts_with line ":" then
            do_command repl.commands line
        else
            let action =
                do eval_thread = thread.new_thread ()
                let eval_action = repl_prim.eval_line line
                repl_prim.finish_or_interrupt repl.cpu_pool eval_thread eval_action
            io.catch action wrap >>= io.println
                *> wrap Continue

    do line_result = rustyline.readline repl.editor "> "
    match line_result with
    | Err Eof -> wrap ()
    | Err Interrupted -> loop repl
    | Ok line ->
        do continue = run_line line
        match continue with
        | Continue -> loop repl
        | Quit ->
            do _ = rustyline.save_history repl.editor
            wrap ()

let run x : () -> IO () =
    do _ = io.println "gluon (:h for help, :q to quit)"
    do editor = rustyline.new_editor ()
    do cpu_pool = repl_prim.new_cpu_pool 1
    let commands = make_commands cpu_pool
    let repl = { commands, editor, cpu_pool }
    loop repl

run