📜 Add better documentation across the compiler. (#3)
These changes pay particular attention to API endpoints, to try to ensure that any rustdocs generated are detailed and sensible. A good next step, eventually, might be to include doctest examples, as well. For the moment, it's not clear that they would provide a lot of value, though. In addition, this does a couple refactors to simplify the code base in ways that make things clearer or, at least, briefer.
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
use crate::syntax::Location;
|
||||
use internment::ArcIntern;
|
||||
use pretty::{DocAllocator, Pretty};
|
||||
use proptest::{
|
||||
@@ -5,13 +6,28 @@ use proptest::{
|
||||
strategy::{BoxedStrategy, Strategy},
|
||||
};
|
||||
|
||||
use crate::syntax::Location;
|
||||
|
||||
/// We're going to represent variables as interned strings.
|
||||
///
|
||||
/// These should be fast enough for comparison that it's OK, since it's going to end up
|
||||
/// being pretty much the pointer to the string.
|
||||
type Variable = ArcIntern<String>;
|
||||
|
||||
/// The representation of a program within our IR. For now, this is exactly one file.
|
||||
///
|
||||
/// In addition, for the moment there's not really much of interest to hold here besides
|
||||
/// the list of statements read from the file. Order is important. In the future, you
|
||||
/// could imagine caching analysis information in this structure.
|
||||
///
|
||||
/// `Program` implements both [`Pretty`] and [`Arbitrary`]. The former should be used
|
||||
/// to print the structure whenever possible, especially if you value your or your
|
||||
/// user's time. The latter is useful for testing that conversions of `Program` retain
|
||||
/// their meaning. All `Program`s generated through [`Arbitrary`] are guaranteed to be
|
||||
/// syntactically valid, although they may contain runtime issue like over- or underflow.
|
||||
#[derive(Debug)]
|
||||
pub struct Program {
|
||||
pub statements: Vec<Statement>,
|
||||
// For now, a program is just a vector of statements. In the future, we'll probably
|
||||
// extend this to include a bunch of other information, but for now: just a list.
|
||||
pub(crate) statements: Vec<Statement>,
|
||||
}
|
||||
|
||||
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Program
|
||||
@@ -23,6 +39,8 @@ where
|
||||
let mut result = allocator.nil();
|
||||
|
||||
for stmt in self.statements.iter() {
|
||||
// there's probably a better way to do this, rather than constantly
|
||||
// adding to the end, but this works.
|
||||
result = result
|
||||
.append(stmt.pretty(allocator))
|
||||
.append(allocator.text(";"))
|
||||
@@ -39,11 +57,21 @@ impl Arbitrary for Program {
|
||||
|
||||
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
|
||||
crate::syntax::Program::arbitrary_with(args)
|
||||
.prop_map(|x| Program::from(x.simplify()))
|
||||
.prop_map(Program::from)
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
|
||||
/// The representation of a statement in the language.
|
||||
///
|
||||
/// For now, this is either a binding site (`x = 4`) or a print statement
|
||||
/// (`print x`). Someday, though, more!
|
||||
///
|
||||
/// As with `Program`, this type implements [`Pretty`], which should
|
||||
/// be used to display the structure whenever possible. It does not
|
||||
/// implement [`Arbitrary`], though, mostly because it's slightly
|
||||
/// complicated to do so.
|
||||
///
|
||||
#[derive(Debug)]
|
||||
pub enum Statement {
|
||||
Binding(Location, Variable, Expression),
|
||||
@@ -71,6 +99,18 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// The representation of an expression.
|
||||
///
|
||||
/// Note that expressions, like everything else in this syntax tree,
|
||||
/// supports [`Pretty`], and it's strongly encouraged that you use
|
||||
/// that trait/module when printing these structures.
|
||||
///
|
||||
/// Also, Expressions at this point in the compiler are explicitly
|
||||
/// defined so that they are *not* recursive. By this point, if an
|
||||
/// expression requires some other data (like, for example, invoking
|
||||
/// a primitive), any subexpressions have been bound to variables so
|
||||
/// that the referenced data will always either be a constant or a
|
||||
/// variable reference.
|
||||
#[derive(Debug)]
|
||||
pub enum Expression {
|
||||
Value(Location, Value),
|
||||
@@ -107,6 +147,12 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// A type representing the primitives allowed in the language.
|
||||
///
|
||||
/// Having this as an enumeration avoids a lot of "this should not happen"
|
||||
/// cases, but might prove to be cumbersome in the future. If that happens,
|
||||
/// this may either become a more hierarchical enumeration, or we'll just
|
||||
/// deal with the "this should not happen" cases.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Primitive {
|
||||
Plus,
|
||||
@@ -144,6 +190,11 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// An expression that is always either a value or a reference.
|
||||
///
|
||||
/// This is the type used to guarantee that we don't nest expressions
|
||||
/// at this level. Instead, expressions that take arguments take one
|
||||
/// of these, which can only be a constant or a reference.
|
||||
#[derive(Debug)]
|
||||
pub enum ValueOrRef {
|
||||
Value(Location, Value),
|
||||
@@ -163,8 +214,23 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ValueOrRef> for Expression {
|
||||
fn from(value: ValueOrRef) -> Self {
|
||||
match value {
|
||||
ValueOrRef::Value(loc, val) => Expression::Value(loc, val),
|
||||
ValueOrRef::Ref(loc, var) => Expression::Reference(loc, var),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A constant in the IR.
|
||||
#[derive(Debug)]
|
||||
pub enum Value {
|
||||
/// A numerical constant.
|
||||
///
|
||||
/// The optional argument is the base that was used by the user to input
|
||||
/// the number. By retaining it, we can ensure that if we need to print the
|
||||
/// number back out, we can do so in the form that the user entered it.
|
||||
Number(Option<u8>, i64),
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user