loalang 0.1.15

Loa is a general-purpose, purely immutable, object-oriented programming language.
Documentation
use crate::*;
use colored::{Color, Colorize};
use loa::assembly::{Assembly, Cursor};
use loa::generation::REPLDirectives;
use loa::semantics::Type;
use loa::server::Server;
use loa::syntax::{characters_to_string, string_to_characters, tokenize, TokenKind};
use loa::vm::VM;
use loa::*;
use rustyline::completion::{Candidate, Completer};
use rustyline::config::Configurer;
use rustyline::error::ReadlineError;
use rustyline::highlight::Highlighter;
use rustyline::hint::Hinter;
use rustyline::{CompletionType, Context, EditMode, Editor, Helper};
use std::borrow::Cow::{Borrowed, Owned};

struct EditorHelper {
    pub server: Arc<Mutex<Server>>,
    pub uri: URI,
}

impl Helper for EditorHelper {}

impl Hinter for EditorHelper {}

#[derive(Debug)]
struct CompletionCandidate {
    pub display: String,
    pub replacement: String,
}

impl Candidate for CompletionCandidate {
    fn display(&self) -> &str {
        self.display.as_ref()
    }

    fn replacement(&self) -> &str {
        self.replacement.as_ref()
    }
}

impl Completer for EditorHelper {
    type Candidate = CompletionCandidate;

    fn complete(
        &self,
        line: &str,
        pos: usize,
        _ctx: &Context<'_>,
    ) -> Result<(usize, Vec<Self::Candidate>), ReadlineError> {
        let chars_before = string_to_characters(line[..pos].into());
        let realpos = chars_before.len();

        let mut server = self.server.lock().unwrap();

        server.set(self.uri.clone(), line.into(), SourceKind::REPLLine);

        if let Some(location) = server.location(&self.uri, (1, realpos + 1)) {
            if let Some(completion) =
                server.completion(location, characters_to_string(chars_before.into_iter()))
            {
                let candidates = match completion {
                    server::Completion::VariablesInScope(prefix, vars) => vars
                        .into_iter()
                        .map(|v| CompletionCandidate {
                            display: format!("{} ({})", v.name, v.type_),
                            replacement: format!("{}", &v.name[prefix.len()..]),
                        })
                        .collect(),

                    server::Completion::Behaviours(prefix, behaviours) => behaviours
                        .into_iter()
                        .map(|behaviour| CompletionCandidate {
                            display: behaviour.to_string(),
                            replacement: format!("{}", &behaviour.selector()[prefix.len()..]),
                        })
                        .collect(),
                };
                return Ok((pos, candidates));
            }
        }
        Ok((0, vec![]))
    }
}

pub fn highlight(source: Arc<Source>, markers: Vec<(Color, Span)>) -> String {
    let tokens = tokenize(source);
    tokens
        .into_iter()
        .map(|token| {
            let lexeme = token.lexeme();

            for (color, span) in markers.iter() {
                if span.contains_location(&token.span.start)
                    && span.contains_location(&token.span.end)
                {
                    return lexeme.color(color.clone()).underline().to_string();
                }
            }

            match token.kind {
                TokenKind::EOF => lexeme,
                TokenKind::Unknown(_) => lexeme.red().underline().to_string(),
                TokenKind::Whitespace(_) => lexeme,

                TokenKind::Arrow
                | TokenKind::FatArrow
                | TokenKind::Period
                | TokenKind::Comma
                | TokenKind::SemiColon
                | TokenKind::OpenCurly
                | TokenKind::CloseCurly
                | TokenKind::OpenParen
                | TokenKind::CloseParen
                | TokenKind::LineComment(_)
                | TokenKind::DocLineMarker
                | TokenKind::DocText(_)
                | TokenKind::DocNewLine(_) => lexeme.bright_black().to_string(),

                TokenKind::SimpleString(_) | TokenKind::SimpleCharacter(_) => {
                    lexeme.green().to_string()
                }

                TokenKind::SymbolLiteral(_) => lexeme.cyan().to_string(),

                TokenKind::AsKeyword
                | TokenKind::InKeyword
                | TokenKind::IsKeyword
                | TokenKind::OutKeyword
                | TokenKind::InoutKeyword
                | TokenKind::ClassKeyword
                | TokenKind::PrivateKeyword
                | TokenKind::PublicKeyword
                | TokenKind::NamespaceKeyword
                | TokenKind::SelfKeyword
                | TokenKind::ImportKeyword
                | TokenKind::ExportKeyword
                | TokenKind::PartialKeyword
                | TokenKind::LetKeyword
                | TokenKind::NativeKeyword
                | TokenKind::PanicKeyword
                | TokenKind::InitKeyword
                | TokenKind::VarKeyword => lexeme.blue().to_string(),
                TokenKind::Dash => lexeme,
                TokenKind::Plus => lexeme,
                TokenKind::Asterisk => lexeme,
                TokenKind::Underscore => lexeme,
                TokenKind::Colon => lexeme,
                TokenKind::Slash => lexeme,
                TokenKind::EqualSign => lexeme,
                TokenKind::OpenAngle => lexeme,
                TokenKind::CloseAngle => lexeme,
                TokenKind::OpenBracket => lexeme,
                TokenKind::CloseBracket => lexeme,
                TokenKind::SimpleInteger(_) | TokenKind::SimpleFloat(_) => {
                    lexeme.magenta().to_string()
                }
                TokenKind::SimpleSymbol(_) => lexeme,
            }
        })
        .collect::<String>()
}

impl Highlighter for EditorHelper {
    fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> {
        let mut server = self.server.lock().unwrap();
        server.set(self.uri.clone(), line.into(), SourceKind::REPLLine);
        let source = server.get(&self.uri).unwrap().source;
        Owned(highlight(source, vec![]))
    }

    fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
        Borrowed(hint)
    }

    fn highlight_candidate<'c>(
        &self,
        candidate: &'c str,
        _completion: CompletionType,
    ) -> Cow<'c, str> {
        Borrowed(candidate)
    }

    fn highlight_char(&self, _line: &str, _pos: usize) -> bool {
        true
    }
}

pub struct REPL {
    editor: Editor<EditorHelper>,
    server: Arc<Mutex<Server>>,
    vm: VM,
    cursor: Cursor,
}

impl REPL {
    pub fn new<R: Reporter>(use_stdlib: bool) -> REPL {
        let mut server = Server::new();

        let mut sources = Source::files("**/*.loa").unwrap_or(vec![]);
        if use_stdlib {
            sources.extend(Source::stdlib().expect("failed to load stdlib"));
        }
        server.add_all(sources.clone());

        let mut failure = false;
        for (_, diagnostics) in server.diagnostics() {
            if Diagnostic::failed(&diagnostics) {
                failure = true;
            }
            R::report(diagnostics, &server.analysis.navigator);
        }

        let mut vm = VM::new();
        let mut cursor = Cursor::new();

        if failure {
            server = Server::new();
        } else {
            match server.generator().generate_all() {
                Err(err) => eprintln!("{:?}", err),
                Ok(i) => {
                    vm.eval::<ServerRuntime>(i.compile(&mut cursor));
                }
            };
        }

        let server = Arc::new(Mutex::new(server));

        let mut editor = Editor::new();
        editor.set_edit_mode(EditMode::Vi);
        editor.set_completion_type(CompletionType::List);
        editor.set_helper(Some(EditorHelper {
            server: server.clone(),
            uri: URI::REPLLine(0),
        }));

        REPL {
            editor,
            server,
            vm,
            cursor,
        }
    }

    pub fn start<R: Reporter>(&mut self) {
        let mut n = 1;
        let mut line = String::new();
        loop {
            let uri = loa::URI::REPLLine(n);
            self.editor.helper_mut().unwrap().uri = uri.clone();
            let addition = match self.editor.readline(
                if line.len() == 0 { ">>> " } else { "... " }
                    .bright_black()
                    .to_string()
                    .as_ref(),
            ) {
                Ok(ref s) if s == "" => continue,
                Ok(line) => line,
                Err(_) => break,
            };
            line.push_str(addition.as_str());

            let mut server = self.server.lock().unwrap();

            server.set(uri.clone(), line.clone(), loa::SourceKind::REPLLine);

            if server.ends_with_syntax_error(&uri) {
                line.push('\n');
                server.remove(uri);
                continue;
            }

            let line = std::mem::replace(&mut line, String::new());
            self.editor.add_history_entry(&line);
            n += 1;

            let mut failure = false;
            for (d_uri, diagnostics) in server.diagnostics() {
                if d_uri == uri {
                    if Diagnostic::failed(&diagnostics) {
                        failure = true;
                    }
                    R::report(diagnostics, &server.analysis.navigator);
                }
            }
            if failure {
                server.remove(uri);
                continue;
            }

            let is_expression = server
                .analysis
                .navigator
                .root_of(&uri)
                .and_then(|repl_line| {
                    if let syntax::REPLLine { mut statements } = repl_line.kind {
                        statements.pop()
                    } else {
                        None
                    }
                })
                .and_then(|statement| server.analysis.navigator.find_node_in(&uri, statement))
                .map(|statement| match statement.kind {
                    syntax::REPLExpression { .. } => true,
                    _ => false,
                })
                .unwrap_or(false);

            let mut assembly = Assembly::new();
            match server.generator().generate(
                REPLDirectivesImpl { vm: &self.vm },
                &mut assembly,
                &uri,
            ) {
                Err(err) => {
                    server.remove(uri);
                    println!("{:?}", err)
                }
                Ok(_) => {
                    if is_expression {
                        if let Some(o) = self
                            .vm
                            .eval_pop::<ServerRuntime>(assembly.compile(&mut self.cursor))
                        {
                            println!("{}", o);
                        }
                    } else {
                        self.vm
                            .eval::<ServerRuntime>(assembly.compile(&mut self.cursor));
                    }
                }
            }
        }
    }
}

struct REPLDirectivesImpl<'a> {
    #[allow(unused)]
    vm: &'a VM,
}

impl<'a> REPLDirectives for REPLDirectivesImpl<'a> {
    fn show_type(&self, type_: Type) {
        println!("{}", type_.to_string().blue());
    }

    fn show_behaviours(&self, type_: Type, types: &semantics::Types) {
        for b in types.get_behaviours(&type_) {
            println!("{}", b.to_string().magenta());
        }
    }
}