📜 Add better documentation across the compiler. #3
@@ -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<String, String>,
|
||||
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<Option<Vec<u8>>, 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<usize>) {
|
||||
term::emit(
|
||||
&mut self.console.lock(),
|
||||
|
||||
80
src/lib.rs
80
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;
|
||||
|
||||
38
src/repl.rs
38
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<String, String>,
|
||||
jitter: Backend<JITModule>,
|
||||
@@ -50,6 +61,12 @@ impl From<REPLError> for Diagnostic<usize> {
|
||||
}
|
||||
|
||||
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<Self, BackendError> {
|
||||
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<usize>,
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user