quail 0.1.0

The Quail Programming Language
use rustyline::error::ReadlineError;

use crate::ast;
use crate::runtime;
use crate::parser;

use ast::Value;
use ast::HoleInfo;
use ast::HoleId;
use ast::Context;
use runtime::Runtime;

pub fn fill(runtime: &mut Runtime, hole_info: &HoleInfo, ctx: Context) -> Value {
    match runtime.holes.get_mut(&hole_info.hole_id) {
        Some(value) => value.clone(),
        None => {
            introduce_hole(hole_info);
            show_bindings(&ctx);
            show_globals(runtime);
            show_holes(runtime, hole_info.hole_id);

            let mut confuse_count = 0;

            loop {
                match runtime.readline() {
                    Ok(line) => {
                        confuse_count = 0;
                        match parse_command(&line) {
                            None => (),
                            Some(command) => match exec_command(runtime, &command, hole_info, &ctx) {
                                None => (),
                                Some(value) => return value,
                            },
                        }
                    },
                    Err(ReadlineError::Interrupted) => {
                        confuse_count += 1;
                        if confuse_count > 1 {
                            println!("Use Ctrl-D to exit.");
                        }
                    },
                    Err(ReadlineError::Eof) => std::process::exit(1),
                    Err(err) => {
                        panic!("Error: {:?}", err);
                    }
                }
            }
        }
    }
}

fn exec_command(
    runtime: &mut Runtime,
    command: &Command,
    hole_info: &HoleInfo,
    ctx: &Context,
) -> Option<Value> {
    match command {
        Command::Fill(term_text) => {
            match parser::parse_term(runtime.next_hole_id(), None, &term_text) {
                Ok((term, number_of_new_holes)) => {
                    runtime.add_holes(number_of_new_holes);
                    let value = runtime.eval(term, ctx.clone());
                    println!("=> {:?}", &value);
                    runtime.fill_hole(hole_info.hole_id, value.clone());
                    return Some(value);
                },
                Err(e) => println!("There was an error {:?}", e),
            }
        },
        Command::Eval(term_text) => {
            match parser::parse_term(runtime.next_hole_id(), None, &term_text) {
                Ok((term, number_of_new_holes)) => {
                    runtime.add_holes(number_of_new_holes);
                    let value = runtime.eval(term, ctx.clone());
                    println!("=> {:?}", &value);
                },
                Err(e) => println!("There was an error {:?}", e),
            }
        },
        Command::Invalid(invalid_cmd) => {
            println!("Invalid command: {}", invalid_cmd);
            println!("Hint: Try 'help' if you don't know what to do.");
        },
        Command::Abort => std::process::exit(1),
        Command::Help => println!("{}", include_str!("../assets/help/hole.txt")),
    }

    None
}

enum Command {
    Fill(String),
    Eval(String),
    Abort,
    Help,
    Invalid(String),
}

fn introduce_hole(hole_info: &HoleInfo) {
    match &hole_info.name {
        None => {
            println!("Encountered hole: #{}     [{}]", hole_info.hole_id, hole_info.loc);
            println!();
        }
        Some(name) => {
            println!("Encountered hole: {}      [{}]", name, hole_info.loc);
            println!();
        }
    }

    if let Some(contents_string) = &hole_info.contents {
        println!("    Note: {:?}", contents_string);
        println!();
    }

}

fn show_bindings(ctx: &Context) {
    println!("    Bindings:");
    for (name, value) in ctx.bindings().into_iter() {
        println!("        {} = {:?}", name, &value);
    }
    println!();
}

fn show_globals(runtime: &Runtime) {
    println!("    Globals:");
    for (name, _value) in runtime.definition_ctx.bindings() {
        println!("        {}", &name);
    }
    println!();
}

fn show_holes(runtime: &Runtime, active_hole_id: HoleId) {
    println!("    Holes:");
    for hole_id in 0..runtime.number_of_holes {
        let hole_id = hole_id as HoleId;
        let leader = if hole_id == active_hole_id {
            "  > "
        } else {
            "    "
        };

        match runtime.hole_value(hole_id) {
            Some(value) => println!("    {}#{} = {:?}", leader, hole_id, value),
            None => println!("    {}#{}", leader, hole_id),
        }
    }
    println!();
}

fn parse_command(line: &str) -> Option<Command> {
    let parts: Vec<String> = line.split(' ').map(|s| s.to_string()).collect();
    if parts.is_empty() {
        None
    } else {
        let command_name = &parts[0];
        if command_name == "fill" {
            let remainder: String = parts[1..].join(" ");
            Some(Command::Fill(remainder))
        } else if command_name =="eval" {
            let remainder: String = parts[1..].join(" ");
            Some(Command::Eval(remainder))
        } else if command_name =="abort" || command_name == "exit" || command_name == "quit" {
            Some(Command::Abort)
        } else if command_name == "help" || command_name == "h" || command_name == "?" {
            Some(Command::Help)
        } else {
            Some(Command::Invalid(command_name.to_string()))
        }

    }
}