UP | HOME

Ponto de Entrada

Arquivo: main.rs.

1. Declarando módulos

#![feature(seek_stream_len)]

pub mod core;
pub mod axioms;
pub mod printing;
pub mod evaluator;
pub mod reader;

#[cfg(test)]
mod tests;

2. Importações

use self::core::{ Maj, MajState };
use self::printing::{ maj_format, maj_format_raw };
use self::evaluator::maj_eval;
use self::reader::tokenizer::maj_tokenize;
use self::reader::parser::maj_parse;

3. REPL

use rustyline::error::ReadlineError;
use rustyline::Editor;
use rustyline::validate::{
    ValidationContext,
    ValidationResult,
    Validator
};
use rustyline_derive::{Completer, Helper, Highlighter, Hinter};
use colored::*;
use std::{ env, process };
#[derive(Completer, Helper, Highlighter, Hinter)]
struct MajInputValidator {}

impl Validator for MajInputValidator {
    fn validate(
        &self,
        ctx: &mut ValidationContext
    ) -> Result<ValidationResult, ReadlineError> {
        use ValidationResult::{
            Incomplete,
            Invalid,
            Valid
        };

        let input = ctx.input();
        let mut ignore_one = false;
        let mut count = 0;
        for c in input.chars() {
            if ignore_one {
                ignore_one = false;
            } else {
                match c {
                    '(' => {
                        count += 1;
                    },
                    ')' => {
                        if count == 0 {
                            count = -1;
                            break;
                        } else {
                            count -= 1;
                        }
                    },
                    '\\' => {
                        // Gambiarra for preventing
                        // incomplete input on characters
                        // such as #\( and #\)
                        ignore_one = true;
                    }
                    _ => {},
                }
            }
        }

        if count > 0 {
            Ok(Incomplete)
        } else if count < 0 {
            Ok(
                Invalid(Some("No matching parenthesis found"
                             .to_owned()))
            )
        } else {
            Ok(Valid(None))
        }
    }
}
fn repl(mut state: &mut MajState, options: &ArgsOptions) {
    use crate::axioms::predicates::maj_errorp;
    if !options.silent {
        println!("Press C-c or C-d to quit");
    }

    let validator = MajInputValidator {};

    let mut rl = Editor::new();
    rl.set_helper(Some(validator));
    let _ = rl.load_history(".majestic_history");
    let mut prompt = if options.silent {
        String::from("")
    } else {
        format!("{}", "> ".green())
    };

    let mut show_tokens = false;

    loop {
        let readline = rl.readline(&prompt);
        match readline {
            Ok(line) => {
                rl.add_history_entry(line.as_str());
                if line == "#t" {
                    show_tokens = !show_tokens;
                    prompt =
                        if options.silent {
                            String::from("")
                        } else if show_tokens {
                            format!("{}", "> ".magenta())
                        } else {
                            format!("{}", "> ".green())
                        };
                } else {
                    //use crate::printing::maj_pretty_format;
                    match maj_tokenize(line.as_ref()) {
                        Ok(tokens) => {
                            if show_tokens {
                                println!("{}",
                                         format!("{:?}", tokens)
                                         .magenta());
                            }
                            match maj_parse(&mut state,
                                                  tokens.clone()) {
                                Ok(expressions) => {
                                    if show_tokens {
                                        println!("{}",
                                                 maj_format_raw(
                                                     &state,
                                                     expressions.clone(),
                                                     false).magenta());
                                    }
                                    use crate::axioms::utils::{
                                        STACK_RED_ZONE,
                                        STACK_PER_RECURSION
                                    };
                                    let results =
                                        stacker::maybe_grow(
                                            STACK_RED_ZONE,
                                            STACK_PER_RECURSION,
                                            || maj_eval(&mut state,
                                                        Maj::cons(
                                                            Maj::do_sym(),
                                                            expressions),
                                                        Maj::nil()));
                                            if !maj_errorp(results.clone()).to_bool() {
                                                println!("{}",
                                                         maj_format(&state, results)
                                                         .cyan());
                                            } else {
                                                eprintln!("{} {}",
                                                          "Error:".red().bold(),
                                                          maj_format(&state, results));
                                            }
                                            
                                        },
                                    Err(msg) =>
                                        eprintln!("{} {}",
                                                  "Parser error:".red().bold(),
                                                  msg),
                                }
                            },
                            Err((line, msg)) =>
                                eprintln!("{} on line {}: {}",
                                          "Syntax error".red().bold(),
                                          line, msg),
                        };
                    }
                },
                Err(ReadlineError::Interrupted) => {
                    if !options.silent {
                        println!("{}", "C-c".cyan().dimmed());
                    }
                    break
                },
                Err(ReadlineError::Eof) => {
                    if !options.silent {
                        println!("{}", "C-d".cyan().dimmed());
                    }
                    break
                },
                Err(err) => {
                    eprintln!("REPL Error: {:?}", err);
                    break
                }
            }
        }

        if let Err(_) = rl.save_history(".majestic_history") {
            eprintln!("Failed saving history to .majestic_history.");
        }

        if !options.silent {
            println!("Quaerendo invenietis.");
        }
    }

4. Parsing de argumentos do console

4.1. Estrutura de argumentos

struct ArgsOptions {
    silent:      bool,
    loadfiles:   Vec<String>,
    programname: String,
    quit:        bool,
    execlines:   Vec<String>,
    showhelp:    bool,
}

4.2. Discriminação dos argumentos

impl ArgsOptions {
    fn empty() -> ArgsOptions {
        ArgsOptions {
            silent:     false,
            loadfiles:   Vec::new(),
            programname: String::new(),
            quit:        false,
            execlines:   Vec::new(),
            showhelp:    false,
        }
    }

    fn new() -> Result<ArgsOptions, String> {
        let mut options = ArgsOptions::empty();

        let mut fetch_load        = false;
        let mut fetch_exec        = false;
        let mut fetch_programname = true;

        for argument in env::args() {
            if fetch_programname {
                options.programname = argument;
                fetch_programname = false;
            } else if fetch_load {
                options.loadfiles.push(argument);
                fetch_load = false;
            } else if fetch_exec {
                options.execlines.push(argument);
                fetch_exec = false;
            } else {
                match argument.as_ref() {
                    "--load" | "-l" => fetch_load = true,
                    "--silent" | "-s" => options.silent = true,
                    "--quit" | "-q" => options.quit = true,
                    "--eval" | "-e" => fetch_exec = true,
                    "--help" | "-h" | "-?" =>
                        options.showhelp = true,
                    "--script" => {
                        options.silent = true;
                        options.quit = true;
                        fetch_load = true;
                    },
                    _ => return Err(
                        format!("Unknown argument: {}", argument)),
                }
            }
        }
        Ok(options)
    }
}

5. Utilitários

5.1. Constantes de compilação

static TIMESTAMP: &'static str = include_str!(
    concat!(env!("OUT_DIR"), "/timestamp.txt"));

static VERSION: &'static str = include_str!(
    concat!(env!("OUT_DIR"), "/version.txt"));

static TARGET: &'static str = include_str!(
    concat!(env!("OUT_DIR"), "/target.txt"));

5.2. Carregando arquivos

fn handle_load_file(options: &ArgsOptions, mut state: &mut MajState) {
    use crate::axioms::primitives::maj_load;
    use crate::axioms::predicates::maj_errorp;
    for file in &options.loadfiles {
        if !options.silent {
            println!("; loading {}...", file);
        }
        let results =
            maj_load(&mut state, Maj::nil(), Maj::string(file));
        if maj_errorp(results.clone()).to_bool() {
            eprintln!("{} {}", "Error:".red().bold(),
                      maj_format(&state, results));
            if options.quit {
                process::exit(1);
            }
        }
    }
}

5.3. Execução de comandos pelo console

fn handle_exec_evals(options: &ArgsOptions, mut state: &mut MajState) {
    for codeline in &options.execlines {
        match maj_tokenize(codeline.as_ref()) {
            Ok(tokens) => {
                match maj_parse(&mut state, tokens.clone()) {
                    Ok(expressions) => {
                        let results =
                            maj_eval(&mut state,
                                     Maj::cons(Maj::do_sym(),
                                               expressions),
                                     Maj::nil());
                        println!("{}", maj_format(&state, results)
                                 .cyan());
                    },
                    Err(msg) => {
                        eprintln!("Parser error: {}", msg)
                    },
                }
            },
            Err((line, msg)) =>
                eprintln!("Syntax error on line {}: {}",
                          line, msg),
        }
    }
}

5.4. Texto de ajuda

fn show_help(programname: String) {
    if VERSION == "" {
        println!("Built at {}", TIMESTAMP);
    }
    println!("Usage: {} [options]
Options:
\t-l, --load [file]   Load and execute contents of file
\t-s, --silent        Do not show ribbon nor REPL prompt
\t-q, --quit          Halt interpreter execution after executing
\t                    commands given through arguments
\t-e, --eval [text]   Evaluate given string of text
\t-h, -?, --help      Show this help text
\t--script [file]     Same as --silent --quit --load [file]",
             programname);
}

6. Função main

fn main() {
    match ArgsOptions::new() {
        Ok(options) => {
            if !options.silent {
                println!("Majestic Lisp v{} {}",
                         format!("{}{}",
                                 env!("CARGO_PKG_VERSION"),
                                 if VERSION != "" { " nightly" }
                                 else { "" }),
                         TARGET);
                if VERSION != "" {
                    println!("Build {} {}", VERSION, TIMESTAMP);
                }
                println!("Copyright (c) 2020-2023 Lucas S. Vieira");
            }

            if options.showhelp {
                show_help(options.programname);
                process::exit(0);
            }

            let mut state = MajState::new();

            // Load files
            handle_load_file(&options, &mut state);

            // Execute code from console
            handle_exec_evals(&options, &mut state);

            if !options.quit {
                repl(&mut state, &options);
            }
        },
        Err(error) => {
            eprintln!("Arguments error: {}", error);
            show_help(String::from("majestic"));
            process::exit(1);
        }
    }
}

Author: Lucas S. Vieira

Created: 2023-01-27 sex 22:44

Validate