Skip to content
This repository has been archived by the owner on Jun 3, 2021. It is now read-only.

[WIP] Add proper Repl #488

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ atty = { version = "0.2", default-features = false, optional = true }
git-testament = { version = "0.1", optional = true }
rand = { version = "0.7", optional = true }
rodio = { version = "0.11.0", optional = true }
rustyline = { version = "6.2.0", optional = true, default-features = false }
rustyline-derive = { version = "0.3.1", optional = true }
dirs-next = { version = "1.0.1", optional = true }

[dev-dependencies]
env_logger = { version = "0.7", default-features = false }
Expand All @@ -50,20 +53,27 @@ proptest = "^0.9.6"
proptest-derive = "0.1"

[features]
default = ["cc", "codegen", "color-backtrace"]
default = ["cc", "codegen", "color-backtrace", "repl"]
# The `swcc` binary
cc = ["ansi_term", "git-testament", "tempfile", "pico-args", "codegen", "atty"]
codegen = ["cranelift", "cranelift-module", "cranelift-object"]
jit = ["codegen", "cranelift-simplejit"]
# The `swcci` binary
repl = ["jit", "rustyline", "rustyline-derive", "ansi_term", "dirs-next"]
salty = ["rand", "rodio"]
# for internal use
_test_headers = []

[[bin]]
name = "swcc"
path = "src/main.rs"
path = "src/bin/swcc.rs"
required-features = ["cc"]

[[bin]]
name = "swcci"
path = "src/bin/swcci.rs"
required-features = ["repl"]

[[bench]]
name = "examples"
harness = false
Expand Down
9 changes: 9 additions & 0 deletions src/analyze/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,15 @@ impl PureAnalyzer {
pub fn warnings(&mut self) -> VecDeque<CompileWarning> {
std::mem::take(&mut self.error_handler.warnings)
}

/// Return all errors seen so far.
///
/// These errors are consumed and will not be returned if you call
/// `errors()` again.
pub fn errors(&mut self) -> VecDeque<CompileError> {
self.error_handler.by_ref().collect()
}

// I type these a lot
#[inline(always)]
fn err(&mut self, e: SemanticError, l: Location) {
Expand Down
27 changes: 27 additions & 0 deletions src/bin/repl/commands.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use super::Repl;
use std::collections::HashMap;

pub fn default_commands() -> HashMap<&'static str, fn(&mut Repl, &str)> {
let mut map = HashMap::<&'static str, fn(&mut Repl, &str)>::new();
map.insert("help", help_command);
map.insert("h", help_command);
map.insert("quit", quit_command);
map.insert("q", quit_command);
map
}

fn help_command(_repl: &mut Repl, _args: &str) {
print!(
"\
Available commands:
{p}help|h Shows this message
{p}quit|q Quits the repl
",
p = super::PREFIX
);
}

fn quit_command(repl: &mut Repl, _args: &str) {
repl.save_history();
std::process::exit(0)
}
127 changes: 127 additions & 0 deletions src/bin/repl/helper.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
use super::PREFIX;
use ansi_term::Style;
use rustyline::{
completion::{extract_word, Candidate, Completer},
highlight::{Highlighter, MatchingBracketHighlighter},
hint::Hinter,
validate::{ValidationContext, ValidationResult, Validator},
Context,
};
use rustyline_derive::Helper;
use std::borrow::Cow;

#[derive(Helper)]
pub struct ReplHelper {
highlighter: MatchingBracketHighlighter,
commands: Vec<&'static str>,
}

impl ReplHelper {
pub fn new(commands: Vec<&'static str>) -> Self {
Self {
commands,
highlighter: Default::default(),
}
}
}

impl Highlighter for ReplHelper {
fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> {
// TODO: Syntax highlighting.
self.highlighter.highlight(line, pos)
}

fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> {
let hint = Style::new().dimmed().paint(hint);
Cow::Owned(hint.to_string())
jyn514 marked this conversation as resolved.
Show resolved Hide resolved
}

fn highlight_char(&self, line: &str, pos: usize) -> bool {
self.highlighter.highlight_char(line, pos)
}
}

impl Validator for ReplHelper {
fn validate(&self, ctx: &mut ValidationContext) -> rustyline::Result<ValidationResult> {
let input = ctx.input();
let mut stack = vec![];

for c in input.chars() {
match c {
'(' | '[' | '{' => stack.push(c),
')' | ']' | '}' => match (stack.pop(), c) {
(Some('('), ')') | (Some('['), ']') | (Some('{'), '}') => {}
(_, _) => {
return Ok(ValidationResult::Invalid(Some(
"extra closing delimiter".to_string(),
)));
}
},
_ => continue,
}
}

if stack.is_empty() {
Ok(ValidationResult::Valid(None))
} else {
Ok(ValidationResult::Incomplete)
}
}
}

impl Hinter for ReplHelper {
fn hint(&self, line: &str, pos: usize, _ctx: &Context<'_>) -> Option<String> {
let start = &line[..pos];
if !start.starts_with(PREFIX) {
return None;
}
let start = &start[PREFIX.len_utf8()..];
self.commands
.iter()
.find(|cmd| cmd.starts_with(start))
.map(|hint| String::from(&hint[start.len()..]))
}
}

/// Wrapper around a `&'static str` to be used for completion candidates.
pub struct CompletionCandidate {
display: &'static str,
}

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

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

impl Completer for ReplHelper {
type Candidate = CompletionCandidate;

fn complete(
&self,
line: &str,
pos: usize,
_ctx: &Context<'_>,
) -> rustyline::Result<(usize, Vec<Self::Candidate>)> {
let (idx, word) = extract_word(line, pos, None, &[]);
if !line.starts_with(PREFIX) {
return Ok((0, vec![]));
}
let word = word.trim_matches(PREFIX);

let commands = self
.commands
.iter()
.filter(|cmd| cmd.starts_with(word))
.map(|x| CompletionCandidate { display: x })
.collect::<Vec<_>>();

Ok((idx + 1, commands))
}

// TODO: Complete method names, types, etc.
}
Loading