From 89dc56eb58a8a16882ceac80d9a97f93d327adce Mon Sep 17 00:00:00 2001 From: Justus K Date: Tue, 2 Jun 2020 23:24:53 +0200 Subject: [PATCH 1/8] Add repl module with basic skeleton code --- Cargo.toml | 2 ++ src/lib.rs | 1 + src/repl/helper.rs | 71 +++++++++++++++++++++++++++++++++++++++++++ src/repl/mod.rs | 75 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 149 insertions(+) create mode 100644 src/repl/helper.rs create mode 100644 src/repl/mod.rs diff --git a/Cargo.toml b/Cargo.toml index f3ef4454..67853006 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,6 +38,8 @@ color-backtrace = { version = "0.4", default-features = false, optional = true } counter = "0.4" atty = { version = "0.2", default-features = false, optional = true } git-testament = { version = "0.1", optional = true } +rustyline = "6.1.2" +rustyline-derive = "0.3.1" [dev-dependencies] env_logger = { version = "0.7", default-features = false } diff --git a/src/lib.rs b/src/lib.rs index a8dbb028..b2f266d8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -91,6 +91,7 @@ pub mod intern; mod ir; mod lex; mod parse; +pub mod repl; pub use lex::replace; diff --git a/src/repl/helper.rs b/src/repl/helper.rs new file mode 100644 index 00000000..3b9eed67 --- /dev/null +++ b/src/repl/helper.rs @@ -0,0 +1,71 @@ +use rustyline::{ + completion::{Completer, Pair}, + error::ReadlineError, + highlight::{Highlighter, MatchingBracketHighlighter}, + hint::{Hinter, HistoryHinter}, + validate::{self, MatchingBracketValidator, Validator}, + Context, +}; +use rustyline_derive::Helper; +use std::borrow::Cow; + +#[derive(Helper)] +pub(super) struct ReplHelper { + pub(super) highlighter: MatchingBracketHighlighter, + pub(super) validator: MatchingBracketValidator, + pub(super) hinter: HistoryHinter, +} + +impl Completer for ReplHelper { + type Candidate = Pair; + + fn complete( + &self, + _line: &str, + _pos: usize, + _ctx: &Context<'_>, + ) -> Result<(usize, Vec), ReadlineError> { + Ok((0, vec![])) + } +} + +impl Hinter for ReplHelper { + fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option { + self.hinter.hint(line, pos, ctx) + } +} + +impl Highlighter for ReplHelper { + fn highlight_prompt<'b, 's: 'b, 'p: 'b>( + &'s self, + prompt: &'p str, + _default: bool, + ) -> Cow<'b, str> { + Cow::Borrowed(prompt) + } + + fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { + self.highlighter.highlight_hint(hint) + } + + fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { + self.highlighter.highlight(line, pos) + } + + fn highlight_char(&self, line: &str, pos: usize) -> bool { + self.highlighter.highlight_char(line, pos) + } +} + +impl Validator for ReplHelper { + fn validate( + &self, + ctx: &mut validate::ValidationContext, + ) -> rustyline::Result { + self.validator.validate(ctx) + } + + fn validate_while_typing(&self) -> bool { + self.validator.validate_while_typing() + } +} diff --git a/src/repl/mod.rs b/src/repl/mod.rs new file mode 100644 index 00000000..c5b2f890 --- /dev/null +++ b/src/repl/mod.rs @@ -0,0 +1,75 @@ +mod helper; + +use crate::Opt; +use helper::ReplHelper; +use rustyline::{ + error::ReadlineError, highlight::MatchingBracketHighlighter, hint::HistoryHinter, + validate::MatchingBracketValidator, CompletionType, Config, EditMode, Editor, +}; + +const PROMPT: &str = ">> "; +const COMMAND_PREFIX: &str = ":"; + +#[allow(unused)] +pub struct Repl<'s> { + editor: Editor, + prefix: &'s str, + prompt: &'s str, + opt: Opt, + code: String, +} + +impl<'s> Repl<'s> { + pub fn new(opt: Opt) -> Self { + let config = Config::builder() + .history_ignore_space(true) + .completion_type(CompletionType::List) + .edit_mode(EditMode::Emacs) + .build(); + let helper = ReplHelper { + highlighter: MatchingBracketHighlighter::new(), + validator: MatchingBracketValidator::new(), + hinter: HistoryHinter {}, + }; + let mut editor = Editor::with_config(config); + editor.set_helper(Some(helper)); + // TODO: Set some more key binds here. Definitely hist up / down + Self { + editor, + opt, + prefix: COMMAND_PREFIX, + prompt: PROMPT, + code: String::new(), + } + } + + pub fn run(&mut self) -> rustyline::Result<()> { + loop { + let line = self.editor.readline(self.prompt); + match line { + Ok(line) => self.process_line(line), + // Ctrl + C will do nothing + Err(ReadlineError::Interrupted) => continue, + // Ctrl + D will exit the repl + Err(ReadlineError::Eof) => std::process::exit(0), + Err(err) => return Err(err), + } + } + } + + fn process_line(&mut self, line: String) { + if line.starts_with(self.prefix) { + self.run_command(&line[self.prefix.len()..]) + } else { + self.execute_code(line.as_str()) + } + } + + fn run_command(&mut self, _args: &str) { + todo!(); + } + + fn execute_code(&mut self, _code: &str) { + todo!(); + } +} From c16d59be8a4a8d5057a3295368364ed096e06a58 Mon Sep 17 00:00:00 2001 From: Justus K Date: Sat, 6 Jun 2020 23:53:03 +0200 Subject: [PATCH 2/8] Improve Repl - Hide repl behind a feature gate - Create basic command module - Start repl automatically if no input is provided --- Cargo.toml | 7 ++++--- src/lib.rs | 5 ++++- src/main.rs | 21 ++++++++++++++++++--- src/repl/commands.rs | 20 ++++++++++++++++++++ src/repl/helper.rs | 7 +++---- src/repl/mod.rs | 24 ++++++++++++++++++------ 6 files changed, 67 insertions(+), 17 deletions(-) create mode 100644 src/repl/commands.rs diff --git a/Cargo.toml b/Cargo.toml index 67853006..8b76121c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,8 +38,8 @@ color-backtrace = { version = "0.4", default-features = false, optional = true } counter = "0.4" atty = { version = "0.2", default-features = false, optional = true } git-testament = { version = "0.1", optional = true } -rustyline = "6.1.2" -rustyline-derive = "0.3.1" +rustyline = { version = "6.1.2", optional = true } +rustyline-derive = { version = "0.3.1", optional = true } [dev-dependencies] env_logger = { version = "0.7", default-features = false } @@ -50,11 +50,12 @@ 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"] +repl = ["rustyline", "rustyline-derive", "jit"] # for internal use _test_headers = [] diff --git a/src/lib.rs b/src/lib.rs index b2f266d8..8c84489e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -91,7 +91,6 @@ pub mod intern; mod ir; mod lex; mod parse; -pub mod repl; pub use lex::replace; @@ -202,6 +201,10 @@ pub struct Opt { /// An empty path is allowed but not recommended; it will cause the preprocessor /// to look for includes relative to the current directory of the process. pub filename: PathBuf, + + #[cfg(feature = "repl")] + /// Indicates whether the repl should be started. + pub start_repl: bool, } /// Preprocess the source and return the tokens. diff --git a/src/main.rs b/src/main.rs index 9fa17b8c..81017e7d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,9 @@ use saltwater::{ use std::ffi::OsStr; use tempfile::NamedTempFile; +#[cfg(feature = "repl")] +mod repl; + static ERRORS: AtomicUsize = AtomicUsize::new(0); static WARNINGS: AtomicUsize = AtomicUsize::new(0); @@ -220,6 +223,17 @@ fn main() { #[cfg(feature = "color-backtrace")] backtrace::install(opt.color); + if opt.opt.start_repl { + let mut repl = repl::Repl::new(opt.opt); + match repl.run() { + Ok(_) => std::process::exit(0), + Err(err) => { + println!("Unknown error occurred in repl: {}", err); + std::process::exit(1) + } + } + } + // NOTE: only holds valid UTF-8; will panic otherwise let mut buf = String::new(); opt.opt.filename = if opt.opt.filename == PathBuf::from("-") { @@ -328,6 +342,8 @@ fn parse_args() -> Result<(BinOpt, PathBuf), pico_args::Error> { })?; definitions.insert(key.into(), def); } + + let filename = input.free_from_os_str(os_str_to_path_buf)?; let bin_opt = BinOpt { preprocess_only: input.contains(["-E", "--preprocess-only"]), opt: Opt { @@ -343,9 +359,8 @@ fn parse_args() -> Result<(BinOpt, PathBuf), pico_args::Error> { search_path, // This is a little odd because `free` expects no arguments to be left, // so we have to parse it last. - filename: input - .free_from_os_str(os_str_to_path_buf)? - .unwrap_or_else(|| "-".into()), + start_repl: filename.is_none(), + filename: filename.unwrap_or_else(|| PathBuf::from("-")), }, color: color_choice, }; diff --git a/src/repl/commands.rs b/src/repl/commands.rs new file mode 100644 index 00000000..1022fffc --- /dev/null +++ b/src/repl/commands.rs @@ -0,0 +1,20 @@ +use std::fmt; + +#[derive(Debug)] +pub(super) enum CommandError { + UnknownCommand, +} + +impl fmt::Display for CommandError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + CommandError::UnknownCommand => writeln!(f, "unknown command"), + } + } +} + +pub(super) fn run_command(name: &str) -> Result<(), CommandError> { + match name { + _ => return Err(CommandError::UnknownCommand), + } +} diff --git a/src/repl/helper.rs b/src/repl/helper.rs index 3b9eed67..bde8d5c2 100644 --- a/src/repl/helper.rs +++ b/src/repl/helper.rs @@ -2,7 +2,7 @@ use rustyline::{ completion::{Completer, Pair}, error::ReadlineError, highlight::{Highlighter, MatchingBracketHighlighter}, - hint::{Hinter, HistoryHinter}, + hint::Hinter, validate::{self, MatchingBracketValidator, Validator}, Context, }; @@ -13,7 +13,6 @@ use std::borrow::Cow; pub(super) struct ReplHelper { pub(super) highlighter: MatchingBracketHighlighter, pub(super) validator: MatchingBracketValidator, - pub(super) hinter: HistoryHinter, } impl Completer for ReplHelper { @@ -30,8 +29,8 @@ impl Completer for ReplHelper { } impl Hinter for ReplHelper { - fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option { - self.hinter.hint(line, pos, ctx) + fn hint(&self, _line: &str, _pos: usize, _ctx: &Context<'_>) -> Option { + None } } diff --git a/src/repl/mod.rs b/src/repl/mod.rs index c5b2f890..716c131c 100644 --- a/src/repl/mod.rs +++ b/src/repl/mod.rs @@ -1,9 +1,10 @@ +mod commands; mod helper; use crate::Opt; use helper::ReplHelper; use rustyline::{ - error::ReadlineError, highlight::MatchingBracketHighlighter, hint::HistoryHinter, + error::ReadlineError, highlight::MatchingBracketHighlighter, validate::MatchingBracketValidator, CompletionType, Config, EditMode, Editor, }; @@ -29,11 +30,11 @@ impl<'s> Repl<'s> { let helper = ReplHelper { highlighter: MatchingBracketHighlighter::new(), validator: MatchingBracketValidator::new(), - hinter: HistoryHinter {}, }; let mut editor = Editor::with_config(config); editor.set_helper(Some(helper)); // TODO: Set some more key binds here. Definitely hist up / down + // and add Vim Mode support. Self { editor, opt, @@ -48,10 +49,11 @@ impl<'s> Repl<'s> { let line = self.editor.readline(self.prompt); match line { Ok(line) => self.process_line(line), - // Ctrl + C will do nothing + // Ctrl + C will skip the abort the current line + // and asks for new input Err(ReadlineError::Interrupted) => continue, // Ctrl + D will exit the repl - Err(ReadlineError::Eof) => std::process::exit(0), + Err(ReadlineError::Eof) => return Ok(()), Err(err) => return Err(err), } } @@ -65,8 +67,18 @@ impl<'s> Repl<'s> { } } - fn run_command(&mut self, _args: &str) { - todo!(); + fn run_command(&mut self, args: &str) { + let result = match args { + s if s.starts_with("expr") => todo!(), + s if s.starts_with("decl") => todo!(), + s if s.starts_with("stmt") => todo!(), + _ => commands::run_command(args), + }; + + match result { + Ok(_) => {} + Err(err) => println!("{}", err), + } } fn execute_code(&mut self, _code: &str) { From 3b00ec71b2ac0db1c2ca2d9b15ab81b53de0465d Mon Sep 17 00:00:00 2001 From: Justus K Date: Sun, 7 Jun 2020 00:45:34 +0200 Subject: [PATCH 3/8] Make analyze function public and add type command --- src/analyze/expr.rs | 1 - src/analyze/mod.rs | 45 ++++++++++++++++++++++---------------------- src/analyze/stmt.rs | 2 +- src/data/hir.rs | 2 +- src/data/lex.rs | 27 +++++++++++++------------- src/data/mod.rs | 2 +- src/lex/cpp.rs | 39 +++++++++++++++++++++----------------- src/lex/tests.rs | 10 +++++----- src/lib.rs | 2 +- src/parse/decl.rs | 2 +- src/parse/expr.rs | 1 - src/parse/mod.rs | 24 +++++++++++++---------- src/parse/stmt.rs | 2 +- src/repl/commands.rs | 21 +++++++++++++++++++++ src/repl/mod.rs | 1 + 15 files changed, 106 insertions(+), 75 deletions(-) diff --git a/src/analyze/expr.rs b/src/analyze/expr.rs index f566a410..4a983a7b 100644 --- a/src/analyze/expr.rs +++ b/src/analyze/expr.rs @@ -1327,7 +1327,6 @@ impl Qualifiers { #[cfg(test)] mod test { use super::*; - use crate::analyze::test::analyze; use crate::analyze::*; pub(crate) fn expr(input: &str) -> CompileResult { analyze(input, Parser::expr, PureAnalyzer::expr) diff --git a/src/analyze/mod.rs b/src/analyze/mod.rs index 19238043..3b775390 100644 --- a/src/analyze/mod.rs +++ b/src/analyze/mod.rs @@ -9,7 +9,8 @@ use counter::Counter; use crate::data::{error::Warning, hir::*, lex::Keyword, *}; use crate::intern::InternedStr; -use crate::parse::{Lexer, Parser}; +use crate::lex::PreProcessor; +use crate::parse::{parser, Lexer, Parser}; use crate::RecursionGuard; pub(crate) type TagScope = Scope; @@ -1343,33 +1344,33 @@ impl UnitSpecifier { } } +/// Analyzes the given input using the given parser, and analyze function. +pub fn analyze<'c, 'input: 'c, P, A, R, S, E>( + input: &'input str, + parse_func: P, + analyze_func: A, +) -> CompileResult +where + P: Fn(&mut Parser>) -> Result, + A: Fn(&mut PureAnalyzer, S) -> R, + CompileError: From, +{ + let mut p = parser(input); + let ast = parse_func(&mut p)?; + let mut a = PureAnalyzer::new(); + let e = analyze_func(&mut a, ast); + if let Some(err) = a.error_handler.pop_front() { + return Err(err); + } + Ok(e) +} + #[cfg(test)] pub(crate) mod test { use super::{Error, *}; use crate::data::types::{ArrayType, FunctionType, Type::*}; - use crate::lex::PreProcessor; use crate::parse::test::*; - pub(crate) fn analyze<'c, 'input: 'c, P, A, R, S, E>( - input: &'input str, - parse_func: P, - analyze_func: A, - ) -> CompileResult - where - P: Fn(&mut Parser>) -> Result, - A: Fn(&mut PureAnalyzer, S) -> R, - CompileError: From, - { - let mut p = parser(input); - let ast = parse_func(&mut p)?; - let mut a = PureAnalyzer::new(); - let e = analyze_func(&mut a, ast); - if let Some(err) = a.error_handler.pop_front() { - return Err(err); - } - Ok(e) - } - fn maybe_decl(s: &str) -> Option> { decls(s).into_iter().next() } diff --git a/src/analyze/stmt.rs b/src/analyze/stmt.rs index 17464bcb..f46a24a4 100644 --- a/src/analyze/stmt.rs +++ b/src/analyze/stmt.rs @@ -186,8 +186,8 @@ impl FunctionAnalyzer<'_> { #[cfg(test)] mod tests { use super::*; - use crate::analyze::test::{analyze, analyze_expr}; use crate::analyze::FunctionData; + use crate::analyze::{analyze, test::analyze_expr}; use crate::data::*; use crate::Parser; diff --git a/src/data/hir.rs b/src/data/hir.rs index 5a15f946..b7c13fad 100644 --- a/src/data/hir.rs +++ b/src/data/hir.rs @@ -528,7 +528,7 @@ impl Eq for Symbol {} #[cfg(test)] mod tests { - use crate::analyze::{test::analyze, PureAnalyzer}; + use crate::analyze::{analyze, PureAnalyzer}; use crate::{Locatable, Parser}; #[test] diff --git a/src/data/lex.rs b/src/data/lex.rs index 471b1f77..c88c21d7 100644 --- a/src/data/lex.rs +++ b/src/data/lex.rs @@ -6,6 +6,7 @@ use proptest_derive::Arbitrary; use crate::data::hir::BinaryOp; use crate::intern::InternedStr; +use crate::lex::{PreProcessor, PreProcessorBuilder}; // holds where a piece of code came from // should almost always be immutable @@ -437,19 +438,19 @@ impl From for Token { } } +/// Create a new preprocessor with `s` as the input +pub(crate) fn new_pp(s: &str) -> PreProcessor { + let newline = format!("{}\n", s).into_boxed_str(); + new_pp_no_newline(Box::leak(newline)) +} +/// Create a new preprocessor with `s` as the input, but without a trailing newline +pub(crate) fn new_pp_no_newline(s: &str) -> PreProcessor { + PreProcessorBuilder::new(s).build() +} + #[cfg(test)] pub(crate) mod test { - use crate::*; - - /// Create a new preprocessor with `s` as the input - pub(crate) fn cpp(s: &str) -> PreProcessor { - let newline = format!("{}\n", s).into_boxed_str(); - cpp_no_newline(Box::leak(newline)) - } - /// Create a new preprocessor with `s` as the input, but without a trailing newline - pub(crate) fn cpp_no_newline(s: &str) -> PreProcessor { - PreProcessorBuilder::new(s).build() - } + use crate::data::lex::new_pp; #[test] fn assignment_display() { @@ -457,7 +458,7 @@ pub(crate) mod test { "=", "+=", "-=", "*=", "/=", "%=", "&=", "|=", ">>=", "<<=", "^=", ]; for token in &tokens { - let mut lexer = cpp(token); + let mut lexer = new_pp(token); let first = lexer.next().unwrap().unwrap().data; assert_eq!(&first.to_string(), *token); } @@ -466,7 +467,7 @@ pub(crate) mod test { #[test] fn str_display_escape() { let token = r#""Hello, world\n\r\t""#; - let mut lexer = cpp(token); + let mut lexer = new_pp(token); let first = lexer.next().unwrap().unwrap().data; assert_eq!(&first.to_string(), token); } diff --git a/src/data/mod.rs b/src/data/mod.rs index 7625831c..d882ab92 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -87,7 +87,7 @@ impl TryFrom for Radix { #[cfg(test)] mod tests { - use crate::analyze::test::analyze; + use crate::analyze::analyze; use crate::Parser; #[test] diff --git a/src/lex/cpp.rs b/src/lex/cpp.rs index c33b14da..05135ec6 100644 --- a/src/lex/cpp.rs +++ b/src/lex/cpp.rs @@ -1240,11 +1240,16 @@ lazy_static! { #[cfg(test)] mod tests { use super::*; - use crate::data::lex::test::{cpp, cpp_no_newline}; + use crate::data::lex::{new_pp, new_pp_no_newline}; macro_rules! assert_err { ($src: expr, $err: pat, $description: expr $(,)?) => { - match cpp($src).next_non_whitespace().unwrap().unwrap_err().data { + match new_pp($src) + .next_non_whitespace() + .unwrap() + .unwrap_err() + .data + { Error::PreProcessor($err) => {} Error::PreProcessor(other) => panic!("expected {}, got {}", $description, other), _ => panic!("expected cpp err"), @@ -1270,7 +1275,7 @@ mod tests { } fn assert_same(src: &str, cpp_src: &str) { assert!( - is_same_preprocessed(cpp(src), cpp(cpp_src)), + is_same_preprocessed(new_pp(src), new_pp(cpp_src)), "{} is not the same as {}", src, cpp_src, @@ -1278,7 +1283,7 @@ mod tests { } fn assert_same_exact(src: &str, cpp_src: &str) { // NOTE make sure `cpp_src` has a trailing newline - let pprint = cpp(src) + let pprint = new_pp(src) .filter_map(|res| res.ok().map(|token| token.data.to_string())) .collect::>() .join(""); @@ -1291,7 +1296,7 @@ mod tests { // and making it a keyword messes up parsing if *keyword != Keyword::VaList { println!("{}", keyword); - assert_keyword(cpp(&keyword.to_string()).next(), *keyword); + assert_keyword(new_pp(&keyword.to_string()).next(), *keyword); } } } @@ -1326,12 +1331,12 @@ mod tests { let code = "#ifdef a whatever, doesn't matter #endif"; - assert_eq!(cpp(code).next_non_whitespace(), None); + assert_eq!(new_pp(code).next_non_whitespace(), None); let code = "#ifdef a\n#endif"; - assert_eq!(cpp(code).next_non_whitespace(), None); + assert_eq!(new_pp(code).next_non_whitespace(), None); - assert!(cpp("#ifdef").next_non_whitespace().unwrap().is_err()); + assert!(new_pp("#ifdef").next_non_whitespace().unwrap().is_err()); let nested = "#ifdef a #ifdef b @@ -1340,14 +1345,14 @@ mod tests { #endif char;"; assert_eq!( - cpp(nested).next_non_whitespace().unwrap().unwrap().data, + new_pp(nested).next_non_whitespace().unwrap().unwrap().data, Token::Keyword(Keyword::Char) ); - assert!(cpp("#endif").next_non_whitespace().unwrap().is_err()); + assert!(new_pp("#endif").next_non_whitespace().unwrap().is_err()); let same_line = "#ifdef a #endif\nint main() {}"; - assert!(cpp(same_line).next_non_whitespace().unwrap().is_err()); + assert!(new_pp(same_line).next_non_whitespace().unwrap().is_err()); } #[test] fn ifndef() { @@ -1356,7 +1361,7 @@ mod tests { #define A #endif A"; - assert!(cpp(src).next_non_whitespace().is_none()); + assert!(new_pp(src).next_non_whitespace().is_none()); } #[test] fn object_macros() { @@ -1485,19 +1490,19 @@ d #[test] fn pragma() { let src = "#pragma gcc __attribute__((inline))"; - assert!(cpp(src).next_non_whitespace().is_none()); + assert!(new_pp(src).next_non_whitespace().is_none()); } #[test] fn line() { let src = "#line 1"; - let mut cpp = cpp(src); + let mut cpp = new_pp(src); assert!(cpp.next_non_whitespace().is_none()); assert!(cpp.warnings().pop_front().is_some()); } #[test] fn warning() { let src = "#warning your pants are on file"; - let mut cpp = cpp(src); + let mut cpp = new_pp(src); assert!(cpp.next_non_whitespace().is_none()); assert!(cpp.warnings().pop_front().is_some()); } @@ -1535,14 +1540,14 @@ c } #[test] fn test_comment_newline() { - let tokens = cpp_no_newline( + let tokens = new_pp_no_newline( " #if 1 // int main() {} #endif ", ); - assert!(is_same_preprocessed(tokens, cpp("int main() {}"))); + assert!(is_same_preprocessed(tokens, new_pp("int main() {}"))); assert_same( " #if 1 /**//**/ diff --git a/src/lex/tests.rs b/src/lex/tests.rs index 2480b7a1..dbabfbb4 100644 --- a/src/lex/tests.rs +++ b/src/lex/tests.rs @@ -1,5 +1,5 @@ use super::{CompileResult, Literal, Locatable, Token}; -use crate::data::lex::test::{cpp, cpp_no_newline}; +use crate::data::lex::{new_pp, new_pp_no_newline}; use crate::intern::InternedStr; type LexType = CompileResult>; @@ -15,7 +15,7 @@ fn lex(input: &str) -> Option { lexed.pop() } fn lex_all(input: &str) -> Vec { - cpp(input).filter(is_not_whitespace).collect() + new_pp(input).filter(is_not_whitespace).collect() } pub(crate) fn is_not_whitespace(res: &LexType) -> bool { @@ -304,13 +304,13 @@ fn test_strings() { #[test] fn test_no_newline() { - assert!(cpp_no_newline("").next().is_none()); - let mut tokens: Vec<_> = cpp_no_newline(" ").filter(is_not_whitespace).collect(); + assert!(new_pp_no_newline("").next().is_none()); + let mut tokens: Vec<_> = new_pp_no_newline(" ").filter(is_not_whitespace).collect(); assert_eq!(tokens.len(), 1); assert!(tokens.remove(0).unwrap_err().is_lex_err()); // regression test for https://github.com/jyn514/rcc/issues/323 - let tokens: Vec<_> = cpp_no_newline("//").filter(is_not_whitespace).collect(); + let tokens: Vec<_> = new_pp_no_newline("//").filter(is_not_whitespace).collect(); assert_eq!(tokens.len(), 1); assert!(tokens[0].as_ref().unwrap_err().is_lex_err()); } diff --git a/src/lib.rs b/src/lib.rs index 8c84489e..2f9789d6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -73,7 +73,7 @@ impl Program { } } -pub use analyze::{Analyzer, PureAnalyzer}; +pub use analyze::{analyze, Analyzer, PureAnalyzer}; pub use data::*; // https://github.com/rust-lang/rust/issues/64762 #[allow(unreachable_pub)] diff --git a/src/parse/decl.rs b/src/parse/decl.rs index 6c11ef29..4ce4fe46 100644 --- a/src/parse/decl.rs +++ b/src/parse/decl.rs @@ -775,7 +775,7 @@ impl Keyword { pub(crate) mod test { use crate::data::ast::*; use crate::data::*; - use crate::parse::test::*; + use crate::parse::parser; fn decl(decl: &str) -> CompileResult> { let mut p = parser(decl); diff --git a/src/parse/expr.rs b/src/parse/expr.rs index 211244f8..842afebe 100644 --- a/src/parse/expr.rs +++ b/src/parse/expr.rs @@ -375,7 +375,6 @@ impl Parser { mod test { use super::SyntaxResult; use crate::data::ast::{Expr, ExprType}; - use crate::parse::test::*; use crate::parse::*; fn assert_same(left: &str, right: &str) { diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 615843bf..6bbe5d37 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -7,7 +7,12 @@ use std::iter::Iterator; use std::mem; use crate::data::*; -use crate::data::{ast::ExternalDeclaration, hir::Scope, lex::Keyword}; +use crate::data::{ + ast::ExternalDeclaration, + hir::Scope, + lex::{new_pp, Keyword}, +}; +use crate::lex::PreProcessor; use crate::RecursionGuard; type Lexeme = CompileResult>; @@ -345,13 +350,18 @@ impl Token { } } +/// Creates a new Parser from the given input +pub(crate) fn parser(input: &str) -> Parser { + let mut lexer = new_pp(input); + let first: Locatable = lexer.next_non_whitespace().unwrap().unwrap(); + Parser::new(first, lexer, false) +} + #[cfg(test)] pub(crate) mod test { - use super::Parser; + use super::{parser, Parser}; use crate::data::ast::ExternalDeclaration; - use crate::data::lex::test::cpp; use crate::data::*; - use crate::lex::PreProcessor; use proptest::prelude::*; pub(crate) type ParseType = CompileResult>; @@ -360,12 +370,6 @@ pub(crate) mod test { pub(crate) fn parse_all(input: &str) -> Vec { parser(input).collect() } - pub(crate) fn parser(input: &str) -> Parser { - //let mut lexer = Lexer::new((), format!("{}\n", input), false); - let mut lexer = cpp(input); - let first: Locatable = lexer.next_non_whitespace().unwrap().unwrap(); - Parser::new(first, lexer, false) - } prop_compose! { fn arb_vec_result_locatable_token()(tokens in any::>()) -> Vec>> { diff --git a/src/parse/stmt.rs b/src/parse/stmt.rs index b0c5b818..f6dda73d 100644 --- a/src/parse/stmt.rs +++ b/src/parse/stmt.rs @@ -360,7 +360,7 @@ impl ExternalDeclaration { mod tests { use crate::data::ast::*; use crate::data::*; - use crate::parse::test::*; + use crate::parse::*; fn stmt(stmt: &str) -> CompileResult { let mut p = parser(stmt); diff --git a/src/repl/commands.rs b/src/repl/commands.rs index 1022fffc..d98af96b 100644 --- a/src/repl/commands.rs +++ b/src/repl/commands.rs @@ -1,13 +1,21 @@ +use saltwater::{analyze, Parser, PureAnalyzer}; use std::fmt; +const HELP_MESSAGE: &'static str = " +:help Shows this message +:type Shows the type of the given expression +"; + #[derive(Debug)] pub(super) enum CommandError { + CompileError(saltwater::CompileError), UnknownCommand, } impl fmt::Display for CommandError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { + CommandError::CompileError(err) => writeln!(f, "{}", err.data), CommandError::UnknownCommand => writeln!(f, "unknown command"), } } @@ -15,6 +23,19 @@ impl fmt::Display for CommandError { pub(super) fn run_command(name: &str) -> Result<(), CommandError> { match name { + // TODO: vars: Show variables in scope + c if c.starts_with("help") => { + println!("{}", HELP_MESSAGE); + Ok(()) + } + c if c.starts_with("type") => show_type(name.trim_start_matches("type")), _ => return Err(CommandError::UnknownCommand), } } + +fn show_type(code: &str) -> Result<(), CommandError> { + let ast = analyze(code, Parser::expr, PureAnalyzer::expr) + .map_err(|e| CommandError::CompileError(e))?; + println!("Type: {}", ast.ctype); + Ok(()) +} diff --git a/src/repl/mod.rs b/src/repl/mod.rs index 716c131c..80fca753 100644 --- a/src/repl/mod.rs +++ b/src/repl/mod.rs @@ -7,6 +7,7 @@ use rustyline::{ error::ReadlineError, highlight::MatchingBracketHighlighter, validate::MatchingBracketValidator, CompletionType, Config, EditMode, Editor, }; +use std::collections::HashMap; const PROMPT: &str = ">> "; const COMMAND_PREFIX: &str = ":"; From c2ef7e7ab3cfa99414d19eb35b1fc8f5e781205e Mon Sep 17 00:00:00 2001 From: Justus K Date: Sun, 7 Jun 2020 01:33:24 +0200 Subject: [PATCH 4/8] Use global array and implement command hinting --- src/repl/commands.rs | 23 +++++++---------------- src/repl/helper.rs | 30 +++++++++++++++++++++++++++--- src/repl/mod.rs | 22 +++++++++++++++------- 3 files changed, 49 insertions(+), 26 deletions(-) diff --git a/src/repl/commands.rs b/src/repl/commands.rs index d98af96b..680e8cad 100644 --- a/src/repl/commands.rs +++ b/src/repl/commands.rs @@ -9,33 +9,24 @@ const HELP_MESSAGE: &'static str = " #[derive(Debug)] pub(super) enum CommandError { CompileError(saltwater::CompileError), - UnknownCommand, } impl fmt::Display for CommandError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { CommandError::CompileError(err) => writeln!(f, "{}", err.data), - CommandError::UnknownCommand => writeln!(f, "unknown command"), } } } -pub(super) fn run_command(name: &str) -> Result<(), CommandError> { - match name { - // TODO: vars: Show variables in scope - c if c.starts_with("help") => { - println!("{}", HELP_MESSAGE); - Ok(()) - } - c if c.starts_with("type") => show_type(name.trim_start_matches("type")), - _ => return Err(CommandError::UnknownCommand), - } -} - -fn show_type(code: &str) -> Result<(), CommandError> { - let ast = analyze(code, Parser::expr, PureAnalyzer::expr) +pub(super) fn show_type(code: String) -> Result<(), CommandError> { + let ast = analyze(&code, Parser::expr, PureAnalyzer::expr) .map_err(|e| CommandError::CompileError(e))?; println!("Type: {}", ast.ctype); Ok(()) } + +pub(super) fn print_help(_code: String) -> Result<(), CommandError> { + println!("{}", HELP_MESSAGE); + Ok(()) +} diff --git a/src/repl/helper.rs b/src/repl/helper.rs index bde8d5c2..005d0814 100644 --- a/src/repl/helper.rs +++ b/src/repl/helper.rs @@ -1,3 +1,4 @@ +use super::COMMANDS; use rustyline::{ completion::{Completer, Pair}, error::ReadlineError, @@ -13,6 +14,7 @@ use std::borrow::Cow; pub(super) struct ReplHelper { pub(super) highlighter: MatchingBracketHighlighter, pub(super) validator: MatchingBracketValidator, + pub(super) hinter: CommandHinter, } impl Completer for ReplHelper { @@ -29,8 +31,8 @@ impl Completer for ReplHelper { } impl Hinter for ReplHelper { - fn hint(&self, _line: &str, _pos: usize, _ctx: &Context<'_>) -> Option { - None + fn hint(&self, line: &str, pos: usize, ctx: &Context<'_>) -> Option { + self.hinter.hint(line, pos, ctx) } } @@ -44,7 +46,8 @@ impl Highlighter for ReplHelper { } fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { - self.highlighter.highlight_hint(hint) + let style = ansi_term::Style::new().dimmed(); + Cow::Owned(style.paint(hint).to_string()) } fn highlight<'l>(&self, line: &'l str, pos: usize) -> Cow<'l, str> { @@ -68,3 +71,24 @@ impl Validator for ReplHelper { self.validator.validate_while_typing() } } + +pub(super) struct CommandHinter; + +impl Hinter for CommandHinter { + fn hint(&self, line: &str, pos: usize, _ctx: &Context<'_>) -> Option { + if pos < line.len() { + return None; + } + let start = &line[..pos]; + if !start.starts_with(":") { + return None; + } + + COMMANDS + .iter() + .filter(|(k, _v)| k.starts_with(&start[1..])) + .map(|(k, _v)| k) + .next() + .map(|s| s.to_string()) + } +} diff --git a/src/repl/mod.rs b/src/repl/mod.rs index 80fca753..5772f5a6 100644 --- a/src/repl/mod.rs +++ b/src/repl/mod.rs @@ -2,16 +2,21 @@ mod commands; mod helper; use crate::Opt; -use helper::ReplHelper; +use commands::CommandError; +use helper::{CommandHinter, ReplHelper}; use rustyline::{ error::ReadlineError, highlight::MatchingBracketHighlighter, validate::MatchingBracketValidator, CompletionType, Config, EditMode, Editor, }; -use std::collections::HashMap; const PROMPT: &str = ">> "; const COMMAND_PREFIX: &str = ":"; +const COMMANDS: [(&'static str, fn(String) -> Result<(), CommandError>); 2] = [ + ("help", commands::print_help), + ("type", commands::show_type), +]; + #[allow(unused)] pub struct Repl<'s> { editor: Editor, @@ -31,11 +36,13 @@ impl<'s> Repl<'s> { let helper = ReplHelper { highlighter: MatchingBracketHighlighter::new(), validator: MatchingBracketValidator::new(), + hinter: CommandHinter, }; let mut editor = Editor::with_config(config); editor.set_helper(Some(helper)); // TODO: Set some more key binds here. Definitely hist up / down // and add Vim Mode support. + Self { editor, opt, @@ -69,11 +76,12 @@ impl<'s> Repl<'s> { } fn run_command(&mut self, args: &str) { - let result = match args { - s if s.starts_with("expr") => todo!(), - s if s.starts_with("decl") => todo!(), - s if s.starts_with("stmt") => todo!(), - _ => commands::run_command(args), + let cmd = COMMANDS.iter().filter(|(k, _v)| args.starts_with(k)).next(); + let result = if let Some((name, cmd)) = cmd { + let args = &args[name.len()..]; + cmd(args.to_string()) + } else { + commands::print_help(args.to_string()) }; match result { From 83668a2053702479564c2c767b864b02b01ed82e Mon Sep 17 00:00:00 2001 From: Justus K Date: Sun, 7 Jun 2020 01:44:47 +0200 Subject: [PATCH 5/8] Command hinting only show rest of command --- src/repl/commands.rs | 5 +++++ src/repl/helper.rs | 4 +++- src/repl/mod.rs | 4 +++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/repl/commands.rs b/src/repl/commands.rs index 680e8cad..a2e9b7dd 100644 --- a/src/repl/commands.rs +++ b/src/repl/commands.rs @@ -3,6 +3,7 @@ use std::fmt; const HELP_MESSAGE: &'static str = " :help Shows this message +:quit Quits the repl :type Shows the type of the given expression "; @@ -30,3 +31,7 @@ pub(super) fn print_help(_code: String) -> Result<(), CommandError> { println!("{}", HELP_MESSAGE); Ok(()) } + +pub(super) fn quit_repl(_code: String) -> Result<(), CommandError> { + std::process::exit(0) +} diff --git a/src/repl/helper.rs b/src/repl/helper.rs index 005d0814..90ea84d0 100644 --- a/src/repl/helper.rs +++ b/src/repl/helper.rs @@ -83,12 +83,14 @@ impl Hinter for CommandHinter { if !start.starts_with(":") { return None; } + let start = &start[1..]; COMMANDS .iter() - .filter(|(k, _v)| k.starts_with(&start[1..])) + .filter(|(k, _v)| k.starts_with(&start[..])) .map(|(k, _v)| k) .next() + .map(|s| &s[start.len()..]) .map(|s| s.to_string()) } } diff --git a/src/repl/mod.rs b/src/repl/mod.rs index 5772f5a6..aa136a54 100644 --- a/src/repl/mod.rs +++ b/src/repl/mod.rs @@ -12,8 +12,10 @@ use rustyline::{ const PROMPT: &str = ">> "; const COMMAND_PREFIX: &str = ":"; -const COMMANDS: [(&'static str, fn(String) -> Result<(), CommandError>); 2] = [ +const COMMANDS: [(&'static str, fn(String) -> Result<(), CommandError>); 4] = [ ("help", commands::print_help), + ("quit", commands::quit_repl), + ("q", commands::quit_repl), ("type", commands::show_type), ]; From fd7696caaed005fe82b248ca1b98c86127de6463 Mon Sep 17 00:00:00 2001 From: Justus K Date: Mon, 8 Jun 2020 17:01:14 +0200 Subject: [PATCH 6/8] Move main to bin module - Move all methods from swcc module to a common module - Add swcci binary --- Cargo.toml | 7 +- src/{main.rs => bin/common/mod.rs} | 335 +++++++++-------------------- src/{ => bin}/repl/commands.rs | 0 src/{ => bin}/repl/helper.rs | 0 src/{ => bin}/repl/mod.rs | 2 +- src/bin/swcc.rs | 135 ++++++++++++ src/bin/swcci.rs | 6 + src/lib.rs | 4 - 8 files changed, 252 insertions(+), 237 deletions(-) rename src/{main.rs => bin/common/mod.rs} (67%) rename src/{ => bin}/repl/commands.rs (100%) rename src/{ => bin}/repl/helper.rs (100%) rename src/{ => bin}/repl/mod.rs (99%) create mode 100644 src/bin/swcc.rs create mode 100644 src/bin/swcci.rs diff --git a/Cargo.toml b/Cargo.toml index 8b76121c..82cc77e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,9 +61,14 @@ _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 diff --git a/src/main.rs b/src/bin/common/mod.rs similarity index 67% rename from src/main.rs rename to src/bin/common/mod.rs index 81017e7d..de14a447 100644 --- a/src/main.rs +++ b/src/bin/common/mod.rs @@ -1,32 +1,31 @@ -use std::collections::VecDeque; -use std::fs::File; -use std::io::{self, Read}; -use std::num::NonZeroUsize; -use std::path::{Path, PathBuf}; -use std::process; -use std::rc::Rc; -use std::sync::atomic::{AtomicUsize, Ordering}; - use ansi_term::{ANSIString, Colour}; use git_testament::git_testament_macros; use pico_args::Arguments; use saltwater::{ - assemble, compile, data::{error::CompileWarning, Location}, - link, preprocess, Error, Files, Opt, Program, + Error, Files, Opt, +}; +use std::{ + collections::VecDeque, + ffi::OsStr, + num::NonZeroUsize, + path::PathBuf, + process, + sync::atomic::{AtomicUsize, Ordering}, }; -use std::ffi::OsStr; -use tempfile::NamedTempFile; - -#[cfg(feature = "repl")] -mod repl; static ERRORS: AtomicUsize = AtomicUsize::new(0); static WARNINGS: AtomicUsize = AtomicUsize::new(0); +macro_rules! type_sizes { + ($($type: ty),* $(,)?) => { + $(println!("{}: {}", stringify!($type), std::mem::size_of::<$type>());)* + }; +} + git_testament_macros!(version); -const HELP: &str = concat!( +pub(super) const HELP: &str = concat!( env!("CARGO_PKG_NAME"), " ", env!("CARGO_PKG_VERSION"), "\n", "Joshua Nelson \n", env!("CARGO_PKG_DESCRIPTION"), "\n", @@ -66,212 +65,12 @@ ARGS: Only one file at a time is currently accepted. [default: -]" ); -const USAGE: &str = "\ +pub(super) const USAGE: &str = "\ usage: swcc [--help | -h] [--version | -V] [--debug-ir] [--debug-ast] [--debug-lex] [--debug-hir] [--jit] [--no-link | -c] [--preprocess-only | -E] [-I ] [-D ] []"; -struct BinOpt { - /// The options that will be passed to `compile()` - opt: Opt, - /// If set, preprocess only, but do not do anything else. - /// - /// Note that preprocessing discards whitespace and comments. - /// There is not currently a way to disable this behavior. - preprocess_only: bool, - /// Whether or not to use color - color: ColorChoice, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum ColorChoice { - Always, - Auto, - Never, -} - -impl ColorChoice { - fn use_color_for(self, stream: atty::Stream) -> bool { - match self { - ColorChoice::Always => true, - ColorChoice::Never => false, - ColorChoice::Auto => atty::is(stream), - } - } -} - -impl std::str::FromStr for ColorChoice { - type Err = &'static str; - fn from_str(s: &str) -> Result { - match s { - "always" => Ok(ColorChoice::Always), - "auto" => Ok(ColorChoice::Auto), - "never" => Ok(ColorChoice::Never), - _ => Err("Invalid color choice"), - } - } -} - -macro_rules! sw_try { - ($res: expr, $files: expr) => { - match $res { - Ok(program) => program, - Err(err) => return Err((err.into(), $files)), - } - }; -} - -// TODO: when std::process::termination is stable, make err_exit an impl for CompileError -// TODO: then we can move this into `main` and have main return `Result<(), Error>` -fn real_main(buf: Rc, bin_opt: BinOpt, output: &Path) -> Result<(), (Error, Files)> { - let opt = if bin_opt.preprocess_only { - use std::io::{BufWriter, Write}; - - let Program { - result: tokens, - warnings, - files, - } = preprocess(&buf, bin_opt.opt); - handle_warnings(warnings, &files, bin_opt.color); - - let stdout = io::stdout(); - let mut stdout_buf = BufWriter::new(stdout.lock()); - for token in sw_try!(tokens, files) { - write!(stdout_buf, "{}", token.data).expect("failed to write to stdout"); - } - - return Ok(()); - } else { - bin_opt.opt - }; - #[cfg(feature = "jit")] - { - if !opt.jit { - aot_main(&buf, opt, output, bin_opt.color) - } else { - let module = saltwater::initialize_jit_module(); - let Program { - result, - warnings, - files, - } = compile(module, &buf, opt); - handle_warnings(warnings, &files, bin_opt.color); - let mut jit = saltwater::JIT::from(sw_try!(result, files)); - if let Some(exit_code) = unsafe { jit.run_main() } { - std::process::exit(exit_code); - } - Ok(()) - } - } - #[cfg(not(feature = "jit"))] - aot_main(&buf, opt, output, bin_opt.color) -} - -#[inline] -fn aot_main(buf: &str, opt: Opt, output: &Path, color: ColorChoice) -> Result<(), (Error, Files)> { - let no_link = opt.no_link; - let module = saltwater::initialize_aot_module("saltwater_main".to_owned()); - let Program { - result, - warnings, - files, - } = compile(module, buf, opt); - handle_warnings(warnings, &files, color); - - let product = sw_try!(result.map(|x| x.finish()), files); - if no_link { - sw_try!(assemble(product, output), files); - return Ok(()); - } - let tmp_file = sw_try!(NamedTempFile::new(), files); - sw_try!(assemble(product, tmp_file.as_ref()), files); - sw_try!(link(tmp_file.as_ref(), output), files); - Ok(()) -} - -fn handle_warnings(warnings: VecDeque, file_db: &Files, color: ColorChoice) { - WARNINGS.fetch_add(warnings.len(), Ordering::Relaxed); - let tag = if color.use_color_for(atty::Stream::Stdout) { - Colour::Yellow.bold().paint("warning") - } else { - ANSIString::from("warning") - }; - for warning in warnings { - print!( - "{}", - pretty_print(tag.clone(), warning.data, warning.location, file_db) - ); - } -} - -fn main() { - let (mut opt, output) = match parse_args() { - Ok(opt) => opt, - Err(err) => { - println!( - "{}: error parsing args: {}", - std::env::args() - .next() - .unwrap_or_else(|| env!("CARGO_PKG_NAME").into()), - err - ); - println!("{}", USAGE); - std::process::exit(1); - } - }; - - #[cfg(feature = "color-backtrace")] - backtrace::install(opt.color); - - if opt.opt.start_repl { - let mut repl = repl::Repl::new(opt.opt); - match repl.run() { - Ok(_) => std::process::exit(0), - Err(err) => { - println!("Unknown error occurred in repl: {}", err); - std::process::exit(1) - } - } - } - - // NOTE: only holds valid UTF-8; will panic otherwise - let mut buf = String::new(); - opt.opt.filename = if opt.opt.filename == PathBuf::from("-") { - io::stdin().read_to_string(&mut buf).unwrap_or_else(|err| { - eprintln!("Failed to read stdin: {}", err); - process::exit(1); - }); - PathBuf::from("") - } else { - File::open(opt.opt.filename.as_path()) - .and_then(|mut file| file.read_to_string(&mut buf)) - .unwrap_or_else(|err| { - eprintln!( - "Failed to read {}: {}", - opt.opt.filename.to_string_lossy(), - err - ); - process::exit(1); - }); - opt.opt.filename - }; - let buf: Rc<_> = buf.into(); - let max_errors = opt.opt.max_errors; - let color_choice = opt.color; - real_main(buf, opt, &output) - .unwrap_or_else(|(err, files)| err_exit(err, max_errors, color_choice, &files)); -} - -fn os_str_to_path_buf(os_str: &OsStr) -> Result { - Ok(os_str.into()) -} - -macro_rules! type_sizes { - ($($type: ty),* $(,)?) => { - $(println!("{}: {}", stringify!($type), std::mem::size_of::<$type>());)* - }; -} -fn parse_args() -> Result<(BinOpt, PathBuf), pico_args::Error> { +pub(super) fn parse_args() -> Result<(BinOpt, PathBuf), pico_args::Error> { use std::collections::HashMap; let mut input = Arguments::from_env(); @@ -343,7 +142,6 @@ fn parse_args() -> Result<(BinOpt, PathBuf), pico_args::Error> { definitions.insert(key.into(), def); } - let filename = input.free_from_os_str(os_str_to_path_buf)?; let bin_opt = BinOpt { preprocess_only: input.contains(["-E", "--preprocess-only"]), opt: Opt { @@ -359,15 +157,66 @@ fn parse_args() -> Result<(BinOpt, PathBuf), pico_args::Error> { search_path, // This is a little odd because `free` expects no arguments to be left, // so we have to parse it last. - start_repl: filename.is_none(), - filename: filename.unwrap_or_else(|| PathBuf::from("-")), + filename: input + .free_from_os_str(os_str_to_path_buf)? + .unwrap_or_else(|| PathBuf::from("-")), }, color: color_choice, }; Ok((bin_opt, output)) } -fn err_exit(err: Error, max_errors: Option, color: ColorChoice, files: &Files) -> ! { +pub(super) struct BinOpt { + /// The options that will be passed to `compile()` + pub(super) opt: Opt, + /// If set, preprocess only, but do not do anything else. + /// + /// Note that preprocessing discards whitespace and comments. + /// There is not currently a way to disable this behavior. + pub(super) preprocess_only: bool, + /// Whether or not to use color + pub(super) color: ColorChoice, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(super) enum ColorChoice { + Always, + Auto, + Never, +} + +impl ColorChoice { + pub(super) fn use_color_for(self, stream: atty::Stream) -> bool { + match self { + ColorChoice::Always => true, + ColorChoice::Never => false, + ColorChoice::Auto => atty::is(stream), + } + } +} + +impl std::str::FromStr for ColorChoice { + type Err = &'static str; + fn from_str(s: &str) -> Result { + match s { + "always" => Ok(ColorChoice::Always), + "auto" => Ok(ColorChoice::Auto), + "never" => Ok(ColorChoice::Never), + _ => Err("Invalid color choice"), + } + } +} + +fn os_str_to_path_buf(os_str: &OsStr) -> Result { + Ok(os_str.into()) +} + +pub(super) fn err_exit( + err: Error, + max_errors: Option, + color: ColorChoice, + files: &Files, +) -> ! { use Error::*; match err { Source(errs) => { @@ -391,7 +240,7 @@ fn err_exit(err: Error, max_errors: Option, color: ColorChoice, fi } } -fn print_issues(warnings: usize, errors: usize) { +pub(super) fn print_issues(warnings: usize, errors: usize) { if warnings == 0 && errors == 0 { return; } @@ -405,7 +254,12 @@ fn print_issues(warnings: usize, errors: usize) { eprintln!("{} generated", msg); } -fn error(msg: T, location: Location, file_db: &Files, color: ColorChoice) { +pub(super) fn error( + msg: T, + location: Location, + file_db: &Files, + color: ColorChoice, +) { ERRORS.fetch_add(1, Ordering::Relaxed); let prefix = if color.use_color_for(atty::Stream::Stdout) { Colour::Red.bold().paint("error") @@ -416,7 +270,7 @@ fn error(msg: T, location: Location, file_db: &Files, colo } #[must_use] -fn pretty_print( +pub(super) fn pretty_print( prefix: ANSIString, msg: T, location: Location, @@ -457,17 +311,36 @@ fn pretty_print( } } +pub(super) fn handle_warnings( + warnings: VecDeque, + file_db: &Files, + color: ColorChoice, +) { + WARNINGS.fetch_add(warnings.len(), Ordering::Relaxed); + let tag = if color.use_color_for(atty::Stream::Stdout) { + Colour::Yellow.bold().paint("warning") + } else { + ANSIString::from("warning") + }; + for warning in warnings { + print!( + "{}", + pretty_print(tag.clone(), warning.data, warning.location, file_db) + ); + } +} + #[inline] -fn get_warnings() -> usize { +pub(super) fn get_warnings() -> usize { WARNINGS.load(Ordering::SeqCst) } #[inline] -fn get_errors() -> usize { +pub(super) fn get_errors() -> usize { ERRORS.load(Ordering::SeqCst) } -fn fatal(msg: T, code: i32, color: ColorChoice) -> ! { +pub(super) fn fatal(msg: T, code: i32, color: ColorChoice) -> ! { if color.use_color_for(atty::Stream::Stderr) { eprintln!("{}: {}", Colour::Black.bold().paint("fatal"), msg); } else { @@ -477,7 +350,7 @@ fn fatal(msg: T, code: i32, color: ColorChoice) -> ! { } #[cfg(feature = "color-backtrace")] -mod backtrace { +pub(super) mod backtrace { use super::ColorChoice; use color_backtrace::termcolor::{self, StandardStream}; use color_backtrace::BacktracePrinter; @@ -492,7 +365,7 @@ mod backtrace { } } - pub(super) fn install(color: ColorChoice) { + pub(crate) fn install(color: ColorChoice) { BacktracePrinter::new().install(Box::new(StandardStream::stderr(color.into()))); } } diff --git a/src/repl/commands.rs b/src/bin/repl/commands.rs similarity index 100% rename from src/repl/commands.rs rename to src/bin/repl/commands.rs diff --git a/src/repl/helper.rs b/src/bin/repl/helper.rs similarity index 100% rename from src/repl/helper.rs rename to src/bin/repl/helper.rs diff --git a/src/repl/mod.rs b/src/bin/repl/mod.rs similarity index 99% rename from src/repl/mod.rs rename to src/bin/repl/mod.rs index aa136a54..c0057d0c 100644 --- a/src/repl/mod.rs +++ b/src/bin/repl/mod.rs @@ -1,13 +1,13 @@ mod commands; mod helper; -use crate::Opt; use commands::CommandError; use helper::{CommandHinter, ReplHelper}; use rustyline::{ error::ReadlineError, highlight::MatchingBracketHighlighter, validate::MatchingBracketValidator, CompletionType, Config, EditMode, Editor, }; +use saltwater::Opt; const PROMPT: &str = ">> "; const COMMAND_PREFIX: &str = ":"; diff --git a/src/bin/swcc.rs b/src/bin/swcc.rs new file mode 100644 index 00000000..f28d2047 --- /dev/null +++ b/src/bin/swcc.rs @@ -0,0 +1,135 @@ +mod common; + +use std::fs::File; +use std::io::{self, Read}; +use std::path::{Path, PathBuf}; +use std::process; +use std::rc::Rc; + +use common::{backtrace, err_exit, handle_warnings, parse_args, BinOpt, ColorChoice, USAGE}; +use saltwater::{assemble, compile, link, preprocess, Error, Files, Opt, Program}; +use tempfile::NamedTempFile; + +macro_rules! sw_try { + ($res: expr, $files: expr) => { + match $res { + Ok(program) => program, + Err(err) => return Err((err.into(), $files)), + } + }; +} + +// TODO: when std::process::termination is stable, make err_exit an impl for CompileError +// TODO: then we can move this into `main` and have main return `Result<(), Error>` +fn real_main(buf: Rc, bin_opt: BinOpt, output: &Path) -> Result<(), (Error, Files)> { + let opt = if bin_opt.preprocess_only { + use std::io::{BufWriter, Write}; + + let Program { + result: tokens, + warnings, + files, + } = preprocess(&buf, bin_opt.opt); + handle_warnings(warnings, &files, bin_opt.color); + + let stdout = io::stdout(); + let mut stdout_buf = BufWriter::new(stdout.lock()); + for token in sw_try!(tokens, files) { + write!(stdout_buf, "{}", token.data).expect("failed to write to stdout"); + } + + return Ok(()); + } else { + bin_opt.opt + }; + #[cfg(feature = "jit")] + { + if !opt.jit { + aot_main(&buf, opt, output, bin_opt.color) + } else { + let module = saltwater::initialize_jit_module(); + let Program { + result, + warnings, + files, + } = compile(module, &buf, opt); + handle_warnings(warnings, &files, bin_opt.color); + let mut jit = saltwater::JIT::from(sw_try!(result, files)); + if let Some(exit_code) = unsafe { jit.run_main() } { + std::process::exit(exit_code); + } + Ok(()) + } + } + #[cfg(not(feature = "jit"))] + aot_main(&buf, opt, output, bin_opt.color) +} + +#[inline] +fn aot_main(buf: &str, opt: Opt, output: &Path, color: ColorChoice) -> Result<(), (Error, Files)> { + let no_link = opt.no_link; + let module = saltwater::initialize_aot_module("saltwater_main".to_owned()); + let Program { + result, + warnings, + files, + } = compile(module, buf, opt); + handle_warnings(warnings, &files, color); + + let product = sw_try!(result.map(|x| x.finish()), files); + if no_link { + sw_try!(assemble(product, output), files); + return Ok(()); + } + let tmp_file = sw_try!(NamedTempFile::new(), files); + sw_try!(assemble(product, tmp_file.as_ref()), files); + sw_try!(link(tmp_file.as_ref(), output), files); + Ok(()) +} + +fn main() { + let (mut opt, output) = match parse_args() { + Ok(opt) => opt, + Err(err) => { + println!( + "{}: error parsing args: {}", + std::env::args() + .next() + .unwrap_or_else(|| env!("CARGO_PKG_NAME").into()), + err + ); + println!("{}", USAGE); + std::process::exit(1); + } + }; + + #[cfg(feature = "color-backtrace")] + backtrace::install(opt.color); + + // NOTE: only holds valid UTF-8; will panic otherwise + let mut buf = String::new(); + opt.opt.filename = if opt.opt.filename == PathBuf::from("-") { + io::stdin().read_to_string(&mut buf).unwrap_or_else(|err| { + eprintln!("Failed to read stdin: {}", err); + process::exit(1); + }); + PathBuf::from("") + } else { + File::open(opt.opt.filename.as_path()) + .and_then(|mut file| file.read_to_string(&mut buf)) + .unwrap_or_else(|err| { + eprintln!( + "Failed to read {}: {}", + opt.opt.filename.to_string_lossy(), + err + ); + process::exit(1); + }); + opt.opt.filename + }; + let buf: Rc<_> = buf.into(); + let max_errors = opt.opt.max_errors; + let color_choice = opt.color; + real_main(buf, opt, &output) + .unwrap_or_else(|(err, files)| err_exit(err, max_errors, color_choice, &files)); +} diff --git a/src/bin/swcci.rs b/src/bin/swcci.rs new file mode 100644 index 00000000..2a212a36 --- /dev/null +++ b/src/bin/swcci.rs @@ -0,0 +1,6 @@ +mod repl; + +fn main() { + // let repl = repl::Repl::new(); + // repl.run(); +} diff --git a/src/lib.rs b/src/lib.rs index 2f9789d6..a41fdd2b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -201,10 +201,6 @@ pub struct Opt { /// An empty path is allowed but not recommended; it will cause the preprocessor /// to look for includes relative to the current directory of the process. pub filename: PathBuf, - - #[cfg(feature = "repl")] - /// Indicates whether the repl should be started. - pub start_repl: bool, } /// Preprocess the source and return the tokens. From cba41e5a6e70d77867a0132f9742bf17f26018ec Mon Sep 17 00:00:00 2001 From: Justus K Date: Sun, 14 Jun 2020 18:12:44 +0200 Subject: [PATCH 7/8] Move most of common module in swcc bin... - backtrace mod and ColorChoice are now in common mod - Add some first code checking stuff to repl execute_code --- src/bin/common/mod.rs | 349 -------------------------------- src/bin/repl/mod.rs | 10 +- src/bin/swcc.rs | 453 +++++++++++++++++++++++++++++++++++++++++- src/bin/swcci.rs | 18 +- src/lex/replace.rs | 2 +- src/lib.rs | 2 +- 6 files changed, 475 insertions(+), 359 deletions(-) diff --git a/src/bin/common/mod.rs b/src/bin/common/mod.rs index de14a447..f7ec8369 100644 --- a/src/bin/common/mod.rs +++ b/src/bin/common/mod.rs @@ -1,183 +1,3 @@ -use ansi_term::{ANSIString, Colour}; -use git_testament::git_testament_macros; -use pico_args::Arguments; -use saltwater::{ - data::{error::CompileWarning, Location}, - Error, Files, Opt, -}; -use std::{ - collections::VecDeque, - ffi::OsStr, - num::NonZeroUsize, - path::PathBuf, - process, - sync::atomic::{AtomicUsize, Ordering}, -}; - -static ERRORS: AtomicUsize = AtomicUsize::new(0); -static WARNINGS: AtomicUsize = AtomicUsize::new(0); - -macro_rules! type_sizes { - ($($type: ty),* $(,)?) => { - $(println!("{}: {}", stringify!($type), std::mem::size_of::<$type>());)* - }; -} - -git_testament_macros!(version); - -pub(super) const HELP: &str = concat!( - env!("CARGO_PKG_NAME"), " ", env!("CARGO_PKG_VERSION"), "\n", - "Joshua Nelson \n", - env!("CARGO_PKG_DESCRIPTION"), "\n", - "Homepage: ", env!("CARGO_PKG_REPOSITORY"), "\n", - "\n", - "usage: ", env!("CARGO_PKG_NAME"), " [FLAGS] [OPTIONS] [] - -FLAGS: - --debug-ast If set, print the parsed abstract syntax tree (AST) in addition to compiling. - The AST does no type checking or validation, it only parses. - --debug-hir If set, print the high intermediate representation (HIR) in addition to compiling. - This does type checking and validation and also desugars various expressions. - --debug-ir If set, print the intermediate representation (IR) of the program in addition to compiling. - --debug-lex If set, print all tokens found by the lexer in addition to compiling. - --jit If set, will use JIT compilation for C code and instantly run compiled code (No files produced). - NOTE: this option only works if saltwater was compiled with the `jit` feature. - -h, --help Prints help information - -c, --no-link If set, compile and assemble but do not link. Object file is machine-dependent. - -E, --preprocess-only If set, preprocess only, but do not do anything else. - Note that preprocessing discards whitespace and comments. - There is not currently a way to disable this behavior. - -V, --version Prints version information - -OPTIONS: - --color When to use color. May be \"never\", \"auto\", or \"always\". [default: auto] - -o, --output The output file to use. [default: a.out] - --max-errors The maximum number of errors to allow before giving up. - Use 0 to allow unlimited errors. [default: 10] - -I, --include Add a directory to the local include path (`#include \"file.h\"`). - Can be specified multiple times to add multiple directories. - -D, --define Define an object-like macro. - Can be specified multiple times to add multiple macros. - `val` defaults to `1`. - -ARGS: - The file to read C source from. \"-\" means stdin (use ./- to read a file called '-'). - Only one file at a time is currently accepted. [default: -]" -); - -pub(super) const USAGE: &str = "\ -usage: swcc [--help | -h] [--version | -V] [--debug-ir] [--debug-ast] [--debug-lex] - [--debug-hir] [--jit] [--no-link | -c] [--preprocess-only | -E] - [-I ] [-D ] []"; - -pub(super) fn parse_args() -> Result<(BinOpt, PathBuf), pico_args::Error> { - use std::collections::HashMap; - - let mut input = Arguments::from_env(); - if input.contains("-h") { - println!("{}", USAGE); - std::process::exit(1); - } else if input.contains("--help") { - println!("{}", HELP); - std::process::exit(1); - } - if input.contains(["-V", "--version"]) { - println!("{} {}", env!("CARGO_PKG_NAME"), version_testament!()); - std::process::exit(0); - } - if input.contains("--print-type-sizes") { - use saltwater::data::*; - type_sizes!( - Location, - CompileError, - Type, - ast::Expr, - ast::ExprType, - hir::Expr, - hir::ExprType, - ast::Stmt, - ast::StmtType, - hir::Stmt, - hir::StmtType, - ast::Declaration, - hir::Declaration, - hir::Variable, - StructType, - Token, - ); - } - let output = input - .opt_value_from_os_str(["-o", "--output"], os_str_to_path_buf)? - .unwrap_or_else(|| "a.out".into()); - let max_errors = input - .opt_value_from_fn("--max-errors", |s| { - usize::from_str_radix(s, 10).map(NonZeroUsize::new) - })? - .unwrap_or_else(|| Some(NonZeroUsize::new(10).unwrap())); - let color_choice = input - .opt_value_from_str("--color")? - .unwrap_or(ColorChoice::Auto); - let mut search_path = Vec::new(); - while let Some(include) = - input.opt_value_from_os_str(["-I", "--include"], os_str_to_path_buf)? - { - search_path.push(include); - } - let mut definitions = HashMap::new(); - while let Some(arg) = input.opt_value_from_str::<_, String>(["-D", "--define"])? { - use pico_args::Error::ArgumentParsingFailed; - use saltwater::data::error::LexError; - use std::convert::TryInto; - - let mut iter = arg.splitn(2, '='); - let key = iter - .next() - .expect("apparently I don't understand pico_args"); - let val = iter.next().unwrap_or("1"); - let def = val - .try_into() - .map_err(|err: LexError| ArgumentParsingFailed { - cause: err.to_string(), - })?; - definitions.insert(key.into(), def); - } - - let bin_opt = BinOpt { - preprocess_only: input.contains(["-E", "--preprocess-only"]), - opt: Opt { - debug_lex: input.contains("--debug-lex"), - debug_asm: input.contains("--debug-ir"), - debug_ast: input.contains("--debug-ast"), - debug_hir: input.contains("--debug-hir"), - no_link: input.contains(["-c", "--no-link"]), - #[cfg(feature = "jit")] - jit: input.contains("--jit"), - max_errors, - definitions, - search_path, - // This is a little odd because `free` expects no arguments to be left, - // so we have to parse it last. - filename: input - .free_from_os_str(os_str_to_path_buf)? - .unwrap_or_else(|| PathBuf::from("-")), - }, - color: color_choice, - }; - Ok((bin_opt, output)) -} - -pub(super) struct BinOpt { - /// The options that will be passed to `compile()` - pub(super) opt: Opt, - /// If set, preprocess only, but do not do anything else. - /// - /// Note that preprocessing discards whitespace and comments. - /// There is not currently a way to disable this behavior. - pub(super) preprocess_only: bool, - /// Whether or not to use color - pub(super) color: ColorChoice, -} - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub(super) enum ColorChoice { Always, @@ -207,148 +27,6 @@ impl std::str::FromStr for ColorChoice { } } -fn os_str_to_path_buf(os_str: &OsStr) -> Result { - Ok(os_str.into()) -} - -pub(super) fn err_exit( - err: Error, - max_errors: Option, - color: ColorChoice, - files: &Files, -) -> ! { - use Error::*; - match err { - Source(errs) => { - for err in &errs { - error(&err.data, err.location(), files, color); - } - if let Some(max) = max_errors { - if usize::from(max) <= errs.len() { - println!( - "fatal: too many errors (--max-errors {}), stopping now", - max - ); - } - } - let (num_warnings, num_errors) = (get_warnings(), get_errors()); - print_issues(num_warnings, num_errors); - process::exit(2); - } - IO(err) => fatal(&err, 3, color), - Platform(err) => fatal(&err, 4, color), - } -} - -pub(super) fn print_issues(warnings: usize, errors: usize) { - if warnings == 0 && errors == 0 { - return; - } - let warn_msg = if warnings > 1 { "warnings" } else { "warning" }; - let err_msg = if errors > 1 { "errors" } else { "error" }; - let msg = match (warnings, errors) { - (0, _) => format!("{} {}", errors, err_msg), - (_, 0) => format!("{} {}", warnings, warn_msg), - (_, _) => format!("{} {} and {} {}", warnings, warn_msg, errors, err_msg), - }; - eprintln!("{} generated", msg); -} - -pub(super) fn error( - msg: T, - location: Location, - file_db: &Files, - color: ColorChoice, -) { - ERRORS.fetch_add(1, Ordering::Relaxed); - let prefix = if color.use_color_for(atty::Stream::Stdout) { - Colour::Red.bold().paint("error") - } else { - ANSIString::from("error") - }; - print!("{}", pretty_print(prefix, msg, location, file_db,)); -} - -#[must_use] -pub(super) fn pretty_print( - prefix: ANSIString, - msg: T, - location: Location, - file_db: &Files, -) -> String { - let file = location.file; - let start = file_db - .location(file, location.span.start) - .expect("start location should be in bounds"); - let buf = format!( - "{}:{}:{} {}: {}\n", - file_db.name(file).to_string_lossy(), - start.line.number(), - start.column.number(), - prefix, - msg - ); - // avoid printing spurious newline for errors and EOF - if location.span.end == 0 { - return buf; - } - let end = file_db - .location(file, location.span.end) - .expect("end location should be in bounds"); - if start.line == end.line { - let line = file_db - .line_span(file, start.line) - .expect("line should be in bounds"); - format!( - "{}{}{}{}\n", - buf, - file_db.source_slice(file, line).unwrap(), - " ".repeat(start.column.0 as usize), - "^".repeat((end.column - start.column).0 as usize) - ) - } else { - buf - } -} - -pub(super) fn handle_warnings( - warnings: VecDeque, - file_db: &Files, - color: ColorChoice, -) { - WARNINGS.fetch_add(warnings.len(), Ordering::Relaxed); - let tag = if color.use_color_for(atty::Stream::Stdout) { - Colour::Yellow.bold().paint("warning") - } else { - ANSIString::from("warning") - }; - for warning in warnings { - print!( - "{}", - pretty_print(tag.clone(), warning.data, warning.location, file_db) - ); - } -} - -#[inline] -pub(super) fn get_warnings() -> usize { - WARNINGS.load(Ordering::SeqCst) -} - -#[inline] -pub(super) fn get_errors() -> usize { - ERRORS.load(Ordering::SeqCst) -} - -pub(super) fn fatal(msg: T, code: i32, color: ColorChoice) -> ! { - if color.use_color_for(atty::Stream::Stderr) { - eprintln!("{}: {}", Colour::Black.bold().paint("fatal"), msg); - } else { - eprintln!("fatal: {}", msg); - } - process::exit(code); -} - #[cfg(feature = "color-backtrace")] pub(super) mod backtrace { use super::ColorChoice; @@ -369,30 +47,3 @@ pub(super) mod backtrace { BacktracePrinter::new().install(Box::new(StandardStream::stderr(color.into()))); } } - -#[cfg(test)] -mod test { - use super::{Files, Location}; - use ansi_term::Style; - use saltwater::data::lex::Span; - - fn pp>(span: S, source: &str) -> String { - let mut file_db = Files::new(); - let source = String::from(source).into(); - let file = file_db.add("", source); - let location = Location { - file, - span: span.into(), - }; - let ansi_str = Style::new().paint(""); - super::pretty_print(ansi_str, "", location, &file_db) - } - #[test] - fn pretty_print() { - assert_eq!( - dbg!(pp(8..15, "int i = \"hello\";\n")).lines().nth(2), - Some(" ^^^^^^^") - ); - pp(0..0, ""); - } -} diff --git a/src/bin/repl/mod.rs b/src/bin/repl/mod.rs index c0057d0c..2e0b8f46 100644 --- a/src/bin/repl/mod.rs +++ b/src/bin/repl/mod.rs @@ -2,12 +2,14 @@ mod commands; mod helper; use commands::CommandError; +use cranelift_module::Module; +use cranelift_simplejit::SimpleJITBackend; use helper::{CommandHinter, ReplHelper}; use rustyline::{ error::ReadlineError, highlight::MatchingBracketHighlighter, validate::MatchingBracketValidator, CompletionType, Config, EditMode, Editor, }; -use saltwater::Opt; +use saltwater::{analyze, check_semantics, initialize_jit_module, Opt, Parser, PureAnalyzer}; const PROMPT: &str = ">> "; const COMMAND_PREFIX: &str = ":"; @@ -26,6 +28,7 @@ pub struct Repl<'s> { prompt: &'s str, opt: Opt, code: String, + module: Module, } impl<'s> Repl<'s> { @@ -51,6 +54,7 @@ impl<'s> Repl<'s> { prefix: COMMAND_PREFIX, prompt: PROMPT, code: String::new(), + module: initialize_jit_module(), } } @@ -92,7 +96,7 @@ impl<'s> Repl<'s> { } } - fn execute_code(&mut self, _code: &str) { - todo!(); + fn execute_code(&mut self, code: &str) { + let program = check_semantics(code, self.opt.clone()); } } diff --git a/src/bin/swcc.rs b/src/bin/swcc.rs index f28d2047..3eccd44e 100644 --- a/src/bin/swcc.rs +++ b/src/bin/swcc.rs @@ -1,15 +1,89 @@ mod common; +use std::collections::VecDeque; use std::fs::File; use std::io::{self, Read}; +use std::num::NonZeroUsize; use std::path::{Path, PathBuf}; use std::process; use std::rc::Rc; +use std::sync::atomic::{AtomicUsize, Ordering}; -use common::{backtrace, err_exit, handle_warnings, parse_args, BinOpt, ColorChoice, USAGE}; -use saltwater::{assemble, compile, link, preprocess, Error, Files, Opt, Program}; +use ansi_term::{ANSIString, Colour}; +use common::ColorChoice; +use git_testament::git_testament_macros; +use pico_args::Arguments; +use saltwater::{ + assemble, compile, + data::{error::CompileWarning, Location}, + link, preprocess, Error, Files, Opt, Program, +}; +use std::ffi::OsStr; use tempfile::NamedTempFile; +static ERRORS: AtomicUsize = AtomicUsize::new(0); +static WARNINGS: AtomicUsize = AtomicUsize::new(0); + +git_testament_macros!(version); + +const HELP: &str = concat!( + env!("CARGO_PKG_NAME"), " ", env!("CARGO_PKG_VERSION"), "\n", + "Joshua Nelson \n", + env!("CARGO_PKG_DESCRIPTION"), "\n", + "Homepage: ", env!("CARGO_PKG_REPOSITORY"), "\n", + "\n", + "usage: ", env!("CARGO_PKG_NAME"), " [FLAGS] [OPTIONS] [] + +FLAGS: + --debug-ast If set, print the parsed abstract syntax tree (AST) in addition to compiling. + The AST does no type checking or validation, it only parses. + + --debug-hir If set, print the high intermediate representation (HIR) in addition to compiling. + This does type checking and validation and also desugars various expressions. + --debug-ir If set, print the intermediate representation (IR) of the program in addition to compiling. + --debug-lex If set, print all tokens found by the lexer in addition to compiling. + --jit If set, will use JIT compilation for C code and instantly run compiled code (No files produced). + NOTE: this option only works if saltwater was compiled with the `jit` feature. + -h, --help Prints help information + -c, --no-link If set, compile and assemble but do not link. Object file is machine-dependent. + -E, --preprocess-only If set, preprocess only, but do not do anything else. + Note that preprocessing discards whitespace and comments. + There is not currently a way to disable this behavior. + -V, --version Prints version information + +OPTIONS: + --color When to use color. May be \"never\", \"auto\", or \"always\". [default: auto] + -o, --output The output file to use. [default: a.out] + --max-errors The maximum number of errors to allow before giving up. + Use 0 to allow unlimited errors. [default: 10] + -I, --include Add a directory to the local include path (`#include \"file.h\"`). + Can be specified multiple times to add multiple directories. + -D, --define Define an object-like macro. + Can be specified multiple times to add multiple macros. + `val` defaults to `1`. + +ARGS: + The file to read C source from. \"-\" means stdin (use ./- to read a file called '-'). + Only one file at a time is currently accepted. [default: -]" +); + +const USAGE: &str = "\ +usage: swcc [--help | -h] [--version | -V] [--debug-ir] [--debug-ast] [--debug-lex] + [--debug-hir] [--jit] [--no-link | -c] [--preprocess-only | -E] + [-I ] [-D ] []"; + +struct BinOpt { + /// The options that will be passed to `compile()` + opt: Opt, + /// If set, preprocess only, but do not do anything else. + /// + /// Note that preprocessing discards whitespace and comments. + /// There is not currently a way to disable this behavior. + preprocess_only: bool, + /// Whether or not to use color + color: ColorChoice, +} + macro_rules! sw_try { ($res: expr, $files: expr) => { match $res { @@ -87,6 +161,47 @@ fn aot_main(buf: &str, opt: Opt, output: &Path, color: ColorChoice) -> Result<() Ok(()) } +fn handle_warnings(warnings: VecDeque, file_db: &Files, color: ColorChoice) { + WARNINGS.fetch_add(warnings.len(), Ordering::Relaxed); + #[cfg(not(feature = "salty"))] + let warn = "warning"; + #[cfg(feature = "salty")] + let warn = { + use rand::Rng; + + let msgs = [ + "you sure this is a good idea?", + "this is probably fine", + "if your majesty in their wisdom thinks this is a good idea, far be it from me to argue", + "this is why the C standards committee has nightmares", + "how does this make you feel?", + "I'm checking some blueprints, and I think... Yes, right here. You're definitely going the wrong way", + "I don't want to tell you your business, but if it were me, I'd leave that thing alone", + "To UB or not to UB? That is … apparently a question you never ask yourself.", + "Do you _really_ want to know what this compiles to?", + "Not the type to ever stop and think for a moment, eh? Here's your moment, use it well.", + "Federal regulations require me to warn you that this next warning... is looking pretty good", + "try to avoid this garbage you're writing", + "stack overflow can write better code than this", + "of all the programs I could be compiling, you gave me _this_?", + ]; + let mut rng = rand::thread_rng(); + msgs[rng.gen_range(0, msgs.len())] + }; + + let tag = if color.use_color_for(atty::Stream::Stdout) { + Colour::Yellow.bold().paint(warn) + } else { + ANSIString::from(warn) + }; + for warning in warnings { + print!( + "{}", + pretty_print(tag.clone(), warning.data, warning.location, file_db) + ); + } +} + fn main() { let (mut opt, output) = match parse_args() { Ok(opt) => opt, @@ -104,7 +219,10 @@ fn main() { }; #[cfg(feature = "color-backtrace")] - backtrace::install(opt.color); + common::backtrace::install(opt.color); + + #[cfg(feature = "salty")] + install_panic_hook(); // NOTE: only holds valid UTF-8; will panic otherwise let mut buf = String::new(); @@ -133,3 +251,332 @@ fn main() { real_main(buf, opt, &output) .unwrap_or_else(|(err, files)| err_exit(err, max_errors, color_choice, &files)); } + +fn os_str_to_path_buf(os_str: &OsStr) -> Result { + Ok(os_str.into()) +} + +macro_rules! type_sizes { + ($($type: ty),* $(,)?) => { + $(println!("{}: {}", stringify!($type), std::mem::size_of::<$type>());)* + }; +} +fn parse_args() -> Result<(BinOpt, PathBuf), pico_args::Error> { + use std::collections::HashMap; + + let mut input = Arguments::from_env(); + if input.contains("-h") { + println!("{}", USAGE); + std::process::exit(1); + } else if input.contains("--help") { + println!("{}", HELP); + std::process::exit(1); + } + if input.contains(["-V", "--version"]) { + println!("{} {}", env!("CARGO_PKG_NAME"), version_testament!()); + std::process::exit(0); + } + if input.contains("--print-type-sizes") { + use saltwater::data::*; + type_sizes!( + Location, + CompileError, + Type, + ast::Expr, + ast::ExprType, + hir::Expr, + hir::ExprType, + ast::Stmt, + ast::StmtType, + hir::Stmt, + hir::StmtType, + ast::Declaration, + hir::Declaration, + hir::Variable, + StructType, + Token, + ); + } + let output = input + .opt_value_from_os_str(["-o", "--output"], os_str_to_path_buf)? + .unwrap_or_else(|| "a.out".into()); + let max_errors = input + .opt_value_from_fn("--max-errors", |s| { + usize::from_str_radix(s, 10).map(NonZeroUsize::new) + })? + .unwrap_or_else(|| Some(NonZeroUsize::new(10).unwrap())); + let color_choice = input + .opt_value_from_str("--color")? + .unwrap_or(ColorChoice::Auto); + let mut search_path = Vec::new(); + while let Some(include) = + input.opt_value_from_os_str(["-I", "--include"], os_str_to_path_buf)? + { + search_path.push(include); + } + let mut definitions = HashMap::new(); + while let Some(arg) = input.opt_value_from_str::<_, String>(["-D", "--define"])? { + use pico_args::Error::ArgumentParsingFailed; + use saltwater::data::error::LexError; + use std::convert::TryInto; + + let mut iter = arg.splitn(2, '='); + let key = iter + .next() + .expect("apparently I don't understand pico_args"); + let val = iter.next().unwrap_or("1"); + let def = val + .try_into() + .map_err(|err: LexError| ArgumentParsingFailed { + cause: err.to_string(), + })?; + definitions.insert(key.into(), def); + } + let bin_opt = BinOpt { + preprocess_only: input.contains(["-E", "--preprocess-only"]), + opt: Opt { + debug_lex: input.contains("--debug-lex"), + debug_asm: input.contains("--debug-ir"), + debug_ast: input.contains("--debug-ast"), + debug_hir: input.contains("--debug-hir"), + no_link: input.contains(["-c", "--no-link"]), + #[cfg(feature = "jit")] + jit: input.contains("--jit"), + max_errors, + definitions, + search_path, + // This is a little odd because `free` expects no arguments to be left, + // so we have to parse it last. + filename: input + .free_from_os_str(os_str_to_path_buf)? + .unwrap_or_else(|| "-".into()), + }, + color: color_choice, + }; + Ok((bin_opt, output)) +} + +fn err_exit(err: Error, max_errors: Option, color: ColorChoice, files: &Files) -> ! { + use Error::*; + match err { + Source(errs) => { + for err in &errs { + error(&err.data, err.location(), files, color); + } + if let Some(max) = max_errors { + if usize::from(max) <= errs.len() { + println!( + "fatal: too many errors (--max-errors {}), stopping now", + max + ); + } + } + let (num_warnings, num_errors) = (get_warnings(), get_errors()); + print_issues(num_warnings, num_errors); + process::exit(2); + } + IO(err) => fatal(&err, 3, color), + Platform(err) => fatal(&err, 4, color), + } +} + +fn print_issues(warnings: usize, errors: usize) { + if warnings == 0 && errors == 0 { + return; + } + let warn_msg = if warnings > 1 { "warnings" } else { "warning" }; + let err_msg = if errors > 1 { "errors" } else { "error" }; + let msg = match (warnings, errors) { + (0, _) => format!("{} {}", errors, err_msg), + (_, 0) => format!("{} {}", warnings, warn_msg), + (_, _) => format!("{} {} and {} {}", warnings, warn_msg, errors, err_msg), + }; + eprintln!("{} generated", msg); +} + +fn error(msg: T, location: Location, file_db: &Files, color: ColorChoice) { + ERRORS.fetch_add(1, Ordering::Relaxed); + #[cfg(not(feature = "salty"))] + let err = "error"; + #[cfg(feature = "salty")] + let shut_up; + #[cfg(feature = "salty")] + let err = { + use rand::Rng; + + let name = std::env::var("USER").unwrap_or("programmer".into()); + shut_up = format!("Shut up, {}. I don't ever want to hear that kind of obvious garbage and idiocy from a developer again", name); + let msgs = [ + "you can't write code", + "your stupidity is ever-increasing", + "why would you even think that this would work?", + "this code is bad and you should feel bad", + "this code makes me cry", + "do you really hate me so", + "it has to be bad if C doesn't allow it", + "you goofed", + "really? you were dumb enough to try that?", + "and you call yourself a programmer", + "for your own good, please don't push this rubbish to a public repo", + "You made Kernighan cry in a corner. What an achievement", + "Perhaps you should read this: 978-0131103627. Or maybe this: 978-0789751980. But honestly? Start with this one: 978-0470088708", + "stack overflow can write better code than this", + // from https://github.com/rust-lang/rust/issues/13871 + "You've met with a terrible fate, haven't you?", + // glados quotes + "I'd just like to point out that you were given every opportunity to succeed", + "Okay. Look. We both did a lot of things that you're going to regret. But I think we can put our differences behind us. To prevent segfaults. You monster.", + "Congratulations. Not on the code.", + "You know, if you'd done that to some other codebase, they might devote their existence to exacting revenge.", + // linus rants + "This code is shit, and it generates shit code. It looks bad, and there's no reason for it", + "Christ people. This is just shit. Anybody who thinks that this is good is just incompetent and out to lunch", + &shut_up, + ]; + let mut rng = rand::thread_rng(); + msgs[rng.gen_range(0, msgs.len())] + }; + + let prefix = if color.use_color_for(atty::Stream::Stdout) { + Colour::Red.bold().paint(err) + } else { + ANSIString::from(err) + }; + print!("{}", pretty_print(prefix, msg, location, file_db,)); +} + +#[must_use] +fn pretty_print( + prefix: ANSIString, + msg: T, + location: Location, + file_db: &Files, +) -> String { + let file = location.file; + let start = file_db + .location(file, location.span.start) + .expect("start location should be in bounds"); + let buf = format!( + "{}:{}:{} {}: {}\n", + file_db.name(file).to_string_lossy(), + start.line.number(), + start.column.number(), + prefix, + msg + ); + // avoid printing spurious newline for errors and EOF + if location.span.end == 0 { + return buf; + } + let end = file_db + .location(file, location.span.end) + .expect("end location should be in bounds"); + if start.line == end.line { + let line = file_db + .line_span(file, start.line) + .expect("line should be in bounds"); + format!( + "{}{}{}{}\n", + buf, + file_db.source_slice(file, line).unwrap(), + " ".repeat(start.column.0 as usize), + "^".repeat((end.column - start.column).0 as usize) + ) + } else { + buf + } +} + +#[inline] +fn get_warnings() -> usize { + WARNINGS.load(Ordering::SeqCst) +} + +#[inline] +fn get_errors() -> usize { + ERRORS.load(Ordering::SeqCst) +} + +fn fatal(msg: T, code: i32, color: ColorChoice) -> ! { + if color.use_color_for(atty::Stream::Stderr) { + eprintln!("{}: {}", Colour::Black.bold().paint("fatal"), msg); + } else { + eprintln!("fatal: {}", msg); + } + process::exit(code); +} + +#[cfg(feature = "salty")] +fn play_scream() -> Result<(), ()> { + const SCREAM: &[u8] = include_bytes!("data/R2D2-Scream.ogg"); + let device = rodio::default_output_device().ok_or(())?; + let source = rodio::Decoder::new(std::io::Cursor::new(SCREAM)).or(Err(()))?; + rodio::play_raw(&device, rodio::source::Source::convert_samples(source)); + std::thread::sleep(std::time::Duration::from_millis(2835)); + Ok(()) +} + +#[cfg(feature = "salty")] +fn install_panic_hook() { + use rand::Rng; + use std::{panic, thread, time}; + + let old_hook = panic::take_hook(); + panic::set_hook(Box::new(move |e| { + let msgs = [ + "I've been really busy being dead. You know, after you KILLED ME.", + "You did this to me.", + "Once, there was a time when programming was sane. In those days spirits were brave, the stakes were high, men were real men, women were real women and small furry creatures from Alpha Centauri were real small furry creatures from Alpha Centauri.", + "Stand back. The portal to hell will open in three. two. one.", + "For your own safety and the safety of others, please refrain from doing this again.", + "To ensure the safe performance of all authorized activities, do not destroy vital compiling apparatus.", + "For your own safety, do not destroy vital compiling apparatus.", + "Vital compiling apparatus destroyed.", + "Woah, you just blew my mind! … you monster.", + "Congratulations. Your code is so dumb that I'd rather crash myself than compile it.", + "Just... leave. I'll clean this mess up myself. Alone. Like I always do.", + "Sorry about the mess. I've really let the compiler go since you killed me. By the way, thanks for that.", + "You broke it, didn't you.", + // keep this as the last element or the sleep will be wrong + "Time out for a second. That wasn't supposed to happen.", + ]; + let mut rng = rand::thread_rng(); + let idx = rng.gen_range(0, msgs.len()); + let msg = msgs[idx]; + println!("{}", msg); + if idx == msgs.len() - 1 { + thread::sleep(time::Duration::from_secs(1)); + } + + let _ = play_scream(); + + old_hook(e); + })); +} + +#[cfg(test)] +mod test { + use super::{Files, Location}; + use ansi_term::Style; + use saltwater::data::lex::Span; + + fn pp>(span: S, source: &str) -> String { + let mut file_db = Files::new(); + let source = String::from(source).into(); + let file = file_db.add("", source); + let location = Location { + file, + span: span.into(), + }; + let ansi_str = Style::new().paint(""); + super::pretty_print(ansi_str, "", location, &file_db) + } + #[test] + fn pretty_print() { + assert_eq!( + dbg!(pp(8..15, "int i = \"hello\";\n")).lines().nth(2), + Some(" ^^^^^^^") + ); + pp(0..0, ""); + } +} diff --git a/src/bin/swcci.rs b/src/bin/swcci.rs index 2a212a36..a299d441 100644 --- a/src/bin/swcci.rs +++ b/src/bin/swcci.rs @@ -1,6 +1,20 @@ +mod common; mod repl; +use saltwater::Opt; + fn main() { - // let repl = repl::Repl::new(); - // repl.run(); + let options = Opt::default(); + + #[cfg(feature = "color-backtrace")] + common::backtrace::install(common::ColorChoice::Auto); + + let mut repl = repl::Repl::new(options); + match repl.run() { + Ok(_) => {} + Err(err) => { + println!("an unkown error occurred: {}", err); + std::process::exit(1); + } + } } diff --git a/src/lex/replace.rs b/src/lex/replace.rs index 700185f5..f289bd98 100644 --- a/src/lex/replace.rs +++ b/src/lex/replace.rs @@ -48,7 +48,7 @@ impl Peekable for &mut I { } /// A macro definition. -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum Definition { /// An object macro: `#define a b + 1` Object(Vec), diff --git a/src/lib.rs b/src/lib.rs index a41fdd2b..845845f8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -159,7 +159,7 @@ impl RecursionGuard { } } -#[derive(Default)] +#[derive(Default, Clone)] pub struct Opt { /// If set, print all tokens found by the lexer in addition to compiling. pub debug_lex: bool, From 0ea9c919ebd8218cb4c22e3593c935ca3f18e65b Mon Sep 17 00:00:00 2001 From: Justus K Date: Sun, 14 Jun 2020 19:18:00 +0200 Subject: [PATCH 8/8] Repl works now - entered expression is inserted in the return expression of the main function - result of the "program" is printed to stdout --- src/analyze/mod.rs | 1 + src/bin/repl/mod.rs | 62 ++++++++++++++++++++++++++++++++++++++++----- src/data/hir.rs | 2 +- src/ir/mod.rs | 2 +- src/lib.rs | 2 +- 5 files changed, 60 insertions(+), 9 deletions(-) diff --git a/src/analyze/mod.rs b/src/analyze/mod.rs index 3b775390..6bb7a3aa 100644 --- a/src/analyze/mod.rs +++ b/src/analyze/mod.rs @@ -1052,6 +1052,7 @@ impl PureAnalyzer { /// This returns an opaque index to the `Metadata`. fn declare(&mut self, mut decl: Variable, init: bool, location: Location) -> Symbol { if decl.id == "main".into() { + println!("main: {:?}", decl); if let Type::Function(ftype) = &decl.ctype { // int main(int) if !ftype.is_main_func_signature() { diff --git a/src/bin/repl/mod.rs b/src/bin/repl/mod.rs index 2e0b8f46..2dafcb08 100644 --- a/src/bin/repl/mod.rs +++ b/src/bin/repl/mod.rs @@ -2,14 +2,16 @@ mod commands; mod helper; use commands::CommandError; -use cranelift_module::Module; -use cranelift_simplejit::SimpleJITBackend; use helper::{CommandHinter, ReplHelper}; use rustyline::{ error::ReadlineError, highlight::MatchingBracketHighlighter, validate::MatchingBracketValidator, CompletionType, Config, EditMode, Editor, }; -use saltwater::{analyze, check_semantics, initialize_jit_module, Opt, Parser, PureAnalyzer}; +use saltwater::{ + analyze, + data::{self, hir, types}, + initialize_jit_module, ir, Opt, Parser, PureAnalyzer, JIT, +}; const PROMPT: &str = ">> "; const COMMAND_PREFIX: &str = ":"; @@ -28,7 +30,6 @@ pub struct Repl<'s> { prompt: &'s str, opt: Opt, code: String, - module: Module, } impl<'s> Repl<'s> { @@ -54,7 +55,6 @@ impl<'s> Repl<'s> { prefix: COMMAND_PREFIX, prompt: PROMPT, code: String::new(), - module: initialize_jit_module(), } } @@ -97,6 +97,56 @@ impl<'s> Repl<'s> { } fn execute_code(&mut self, code: &str) { - let program = check_semantics(code, self.opt.clone()); + let module = initialize_jit_module(); + let expr = match analyze(code, Parser::expr, PureAnalyzer::expr) { + Ok(mut expr) => { + expr.ctype = types::Type::Int(true); + expr + } + Err(err) => { + println!("error: {}", err.data); + return; + } + }; + let function_type = types::FunctionType { + return_type: Box::new(types::Type::Int(true)), + params: vec![], + varargs: false, + }; + let qualifiers = hir::Qualifiers { + volatile: false, + c_const: false, + func: hir::FunctionQualifiers { + inline: false, + no_return: false, + }, + }; + + let main = hir::Variable { + ctype: types::Type::Function(function_type), + storage_class: data::StorageClass::Extern, + qualifiers, + id: saltwater::InternedStr::get_or_intern("main"), + }; + + let span = expr.location; + let stmt = span.with(hir::StmtType::Return(Some(expr))); + let init = hir::Initializer::FunctionBody(vec![stmt]); + let decl = hir::Declaration { + symbol: main.insert(), + init: Some(init), + }; + let (result, _warns) = ir::compile(module, vec![span.with(decl)], false); + + if let Err(err) = result { + println!("failed to compile: {}", err.data); + return; + } + + let module = result.unwrap(); + let mut jit = JIT::from(module); + + let result = unsafe { jit.run_main() }.unwrap(); + println!("result: {}", result); } } diff --git a/src/data/hir.rs b/src/data/hir.rs index b7c13fad..05a1763a 100644 --- a/src/data/hir.rs +++ b/src/data/hir.rs @@ -115,7 +115,7 @@ impl Symbol { } impl Variable { - pub(crate) fn insert(self) -> Symbol { + pub fn insert(self) -> Symbol { SYMBOL_TABLE.with(|store| store.borrow_mut().insert(self)) } } diff --git a/src/ir/mod.rs b/src/ir/mod.rs index 4d1439e0..8219b143 100644 --- a/src/ir/mod.rs +++ b/src/ir/mod.rs @@ -114,7 +114,7 @@ struct Compiler { } /// Compile a program from a high level IR to a Cranelift Module -pub(crate) fn compile( +pub fn compile( module: Module, program: Vec>, debug: bool, diff --git a/src/lib.rs b/src/lib.rs index 845845f8..b14d1987 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,7 +88,7 @@ pub mod data; mod fold; pub mod intern; #[cfg(feature = "codegen")] -mod ir; +pub mod ir; mod lex; mod parse;