diff --git a/src/compiler.rs b/src/compiler.rs index f130e02..a60d5eb 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -9,6 +9,13 @@ use codespan_reporting::{ use pretty::termcolor::{ColorChoice, StandardStream}; use target_lexicon::Triple; +/// A high-level compiler for NGR programs. +/// +/// This object can be built once, and then re-used many times to build multiple +/// files. For most users, the [`Default`] implementation should be sufficient; +/// it will use `stderr` for warnings and errors, with default colors based on +/// what we discover from the terminal. For those who want to provide alternate +/// outputs, though, the `Compiler::new` constructor is available. pub struct Compiler { file_database: SimpleFiles, console: StandardStream, @@ -53,8 +60,19 @@ impl Compiler { } } - // This is the actual meat of the compilation chain; we hide it from the user - // because the type is kind of unpleasant. + /// This is the actual meat of the compilation chain; we hide it from the user + /// because the type is kind of unpleasant. + /// + /// The weird error type comes from the fact that we can run into three types + /// of result: + /// + /// * Fundamental errors, like an incorrectly formatted file or some + /// oddity with IO. These return `Err`. + /// * Validation errors, where we reject the program due to something + /// semantically wrong with them. These return `Ok(None)`. + /// * Success! In this case, we return `Ok(Some(...))`, where the bytes + /// returned is the contents of the compiled object file. + /// fn compile_internal(&mut self, input_file: &str) -> Result>, CompilerError> { // Try to parse the file into our syntax AST. If we fail, emit the error // and then immediately return `None`. @@ -92,6 +110,10 @@ impl Compiler { Ok(Some(backend.bytes()?)) } + /// Emit a diagnostic. + /// + /// This is just a really handy shorthand we use elsewhere in the object, because + /// there's a lot of boilerplate we'd like to skip. fn emit(&mut self, diagnostic: Diagnostic) { term::emit( &mut self.console.lock(), diff --git a/src/lib.rs b/src/lib.rs index d0a4589..88057b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,9 +1,79 @@ -pub mod backend; -mod compiler; -pub mod eval; -pub mod ir; -mod repl; +//! # NGR (No Good Reason) Compiler +//! +//! This is the top-level module for the NGR compiler; a compiler written +//! in Rust for no good reason. I may eventually try to turn this into a +//! basic guide for writing compilers, but for now it's a fairly silly +//! (although complete) language and implementation, featuring: +//! +//! * Variable binding with basic arithmetic operators. +//! * The ability to print variable values. +//! +//! I'll be extending this list into the future, with the eventual goal of +//! being able to implement basic programming tasks with it. For example, +//! I have a goal of eventually writing reasonably-clear +//! [Advent of Code](https://adventofcode.com/) implementations with it. +//! +//! Users of this as a library will want to choose their adventure based +//! on how much they want to customize their experience; I've defaulted +//! to providing the ability to see internals, rather than masking them, +//! so folks can play with things as they see fit. +//! +//! ## Easy Mode - Just Running a REPL or Compiler +//! +//! For easiest use, you will want to use either the [`Compiler`] object +//! or the [`REPL`] object. +//! +//! As you might expect, the [`Compiler`] object builds a compiler, which +//! can be re-used to compile as many files as you'd like. Right now, +//! that's all it does. (TODO: Add a linker function to it.) +//! +//! The [`REPL`] object implements the core of what you'll need to +//! implement a just-in-time compiled read-eval-print loop. It will +//! maintain variable state and make sure that variables are linked +//! appropriately as the loop progresses. +//! +//! ## Hard Mode - Looking at the individual passes +//! +//! This compiler is broken into three core parts: +//! +//! 1. The front-end / syntax engine. This portion of the compiler is +//! responsible for turning basic strings (or files) into a machine- +//! friendly abstract syntax tree. See the [`syntax`] module for +//! more information. +//! 2. The IR. This portion of the compiler will be responsible for +//! high-level code analysis and transformation ... although for +//! now, it doesn't do much at all. See the [`ir`] module for more +//! information. +//! 3. The Backend implementation. This portion of the compiler turns +//! the IR from the previous section into Cranelift structures, and +//! helps with either compiling them via JIT or statically compiling +//! them into a file. The [`backend`] module also contains information +//! about the runtime functions made available to the user. +//! +//! ## Testing +//! +//! Testing is a key focus of this effort. To that end, both the syntax +//! tree used in the syntax module and the IR used in the middle of the +//! compiler both implement `Arbitrary`, and are subject to property-based +//! testing to make sure that various passes work properly. +//! +//! In addition, to support basic equivalence testing, we include support +//! for evaluating all expressions. The [`eval`] module provides some +//! utility support for this work. +//! +/// The front-end of the compiler: lexing, parsing, validation pub mod syntax; +/// The middle of the compiler: analysis, simplification, optimization +pub mod ir; +/// The backend of the compiler: transformation to Cranelift, runtime +pub mod backend; +/// Helpful functions for evaluating NGR programs +pub mod eval; + +/// Implementation module for the high-level compiler +mod compiler; +/// Implementation module for the high-level REPL +mod repl; pub use crate::compiler::Compiler; pub use crate::repl::REPL; diff --git a/src/repl.rs b/src/repl.rs index 1270b84..84ec075 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -9,6 +9,17 @@ use cranelift_module::ModuleError; use pretty::termcolor::{ColorChoice, StandardStream}; use std::collections::HashMap; +/// A high-level REPL helper for NGR. +/// +/// This object holds most of the state required to implement some +/// form of interactive compiler for NGR; all you need to do is provide +/// the actual user IO. +/// +/// For most console-based used cases, the [`Default`] implementation +/// should be sufficient; it prints any warnings or errors to `stdout`, +/// using a default color scheme that should work based on the terminal +/// type. For more complex interactions, though, you may want to use +/// the `REPL::new` function to provide your own print substrate. pub struct REPL { file_database: SimpleFiles, jitter: Backend, @@ -50,6 +61,12 @@ impl From for Diagnostic { } impl REPL { + /// Construct a new REPL helper, using the given stream implementation and console configuration. + /// + /// For most users, the [`Default::default`] implementation will be sufficient; + /// it will use `stdout` and a default console configuration. But if you need to + /// be more specific, this will help you provide more guidance to the REPL as it + /// evaluates things. pub fn new(console: StandardStream, console_config: Config) -> Result { Ok(REPL { file_database: SimpleFiles::new(), @@ -61,6 +78,10 @@ impl REPL { }) } + /// Emit a diagnostic to the configured console. + /// + /// This is just a convenience function; there's a lot of boilerplate in printing + /// diagnostics, and it was nice to pull it out into its own function. fn emit_diagnostic( &mut self, diagnostic: Diagnostic, @@ -73,6 +94,17 @@ impl REPL { ) } + /// Process a line of input, printing any problems or the results. + /// + /// The line number argument is just for a modicum of source information, to + /// provide to the user if some parsing or validation step fails. It can be + /// changed to be any value you like that provides some insight into what + /// failed, although it is probably a good idea for it to be different for + /// every invocation of this function. (Not critical, but a good idea.) + /// + /// Any warnings or errors generated in processing this command will be + /// printed to the configured console. If there are no problems, the + /// command will be compiled and then executed. pub fn process_input(&mut self, line_no: usize, command: String) { if let Err(err) = self.process(line_no, command) { if let Err(e) = self.emit_diagnostic(Diagnostic::from(err)) { @@ -84,6 +116,12 @@ impl REPL { } } + /// The internal implementation, with a handy `Result` type. + /// + /// All information from the documentation of `REPL::process_input` applies here, + /// as well; this is the internal implementation of that function, which is + /// differentiated by returning a `Result` type that is hidden from the user + /// in the case of `REPL::process_input`. fn process(&mut self, line_no: usize, command: String) -> Result<(), REPLError> { let entry = self.file_database.add("entry".to_string(), command); let source = self