checkpoint; builds again

This commit is contained in:
2023-12-02 22:38:44 -08:00
parent 71228b9e09
commit 93cac44a99
16 changed files with 1200 additions and 1194 deletions

View File

@@ -9,36 +9,58 @@ use proptest::{
prelude::Arbitrary,
strategy::{BoxedStrategy, Strategy},
};
use std::{fmt, str::FromStr};
use std::{fmt, str::FromStr, sync::atomic::AtomicUsize};
/// 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>;
pub type Variable = ArcIntern<String>;
/// Generate a new symbol that is guaranteed to be different from every other symbol
/// currently known.
///
/// This function will use the provided string as a base name for the symbol, but
/// extend it with numbers and characters to make it unique. While technically you
/// could roll-over these symbols, you probably don't need to worry about it.
pub fn gensym(base: &str) -> Variable {
static COUNTER: AtomicUsize = AtomicUsize::new(0);
ArcIntern::new(format!(
"{}<{}>",
base,
COUNTER.fetch_add(1, std::sync::atomic::Ordering::SeqCst)
))
}
/// 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.
/// A program consists of a series of statements and functions. The statements should
/// be executed in order. The functions currently may not reference any variables
/// at the top level, so their order only matters in relation to each other (functions
/// may not be referenced before they are defined).
///
/// `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.
///
/// The type variable is, somewhat confusingly, the current definition of a type within
/// the IR. Since the makeup of this structure may change over the life of the compiler,
/// it's easiest to just make it an argument.
#[derive(Debug)]
pub struct Program {
pub struct Program<Type> {
// 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) items: Vec<TopLevel>,
pub(crate) items: Vec<TopLevel<Type>>,
}
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Program
impl<'a, 'b, D, A, Type> Pretty<'a, D, A> for &'b Program<Type>
where
A: 'a,
D: ?Sized + DocAllocator<'a, A>,
&'b Type: Pretty<'a, D, A>,
{
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
let mut result = allocator.nil();
@@ -56,17 +78,18 @@ where
}
}
impl Arbitrary for Program {
impl<Type: core::fmt::Debug> Arbitrary for Program<Type> {
type Parameters = crate::syntax::arbitrary::GenerationEnvironment;
type Strategy = BoxedStrategy<Self>;
fn arbitrary_with(args: Self::Parameters) -> Self::Strategy {
crate::syntax::Program::arbitrary_with(args)
.prop_map(|x| {
x.type_infer()
.expect("arbitrary_with should generate type-correct programs")
})
.boxed()
unimplemented!()
//crate::syntax::Program::arbitrary_with(args)
// .prop_map(|x| {
// x.type_infer()
// .expect("arbitrary_with should generate type-correct programs")
// })
// .boxed()
}
}
@@ -76,84 +99,35 @@ impl Arbitrary for Program {
/// will likely be added in the future, but for now: just statements
/// and functions
#[derive(Debug)]
pub enum TopLevel {
Statement(Statement),
Function(Variable, Vec<Variable>, Vec<Statement>, Expression),
pub enum TopLevel<Type> {
Statement(Expression<Type>),
Function(Variable, Vec<(Variable, Type)>, Type, Expression<Type>),
}
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b TopLevel
impl<'a, 'b, D, A, Type> Pretty<'a, D, A> for &'b TopLevel<Type>
where
A: 'a,
D: ?Sized + DocAllocator<'a, A>,
&'b Type: Pretty<'a, D, A>,
{
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
match self {
TopLevel::Function(name, args, stmts, expr) => {
let base = allocator
.text("function")
.append(allocator.space())
.append(allocator.text(name.as_ref().to_string()))
.append(allocator.space())
.append(
pretty_comma_separated(
allocator,
&args.iter().map(PrettySymbol::from).collect(),
)
.parens(),
)
.append(allocator.space());
let mut body = allocator.nil();
for stmt in stmts {
body = body
.append(stmt.pretty(allocator))
.append(allocator.text(";"))
.append(allocator.hardline());
}
body = body.append(expr.pretty(allocator));
body = body.append(allocator.hardline());
body = body.braces();
base.append(body)
}
TopLevel::Statement(stmt) => stmt.pretty(allocator),
}
}
}
/// 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, Type, Expression),
Print(Location, Type, Variable),
}
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Statement
where
A: 'a,
D: ?Sized + DocAllocator<'a, A>,
{
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
match self {
Statement::Binding(_, var, _, expr) => allocator
.text(var.as_ref().to_string())
TopLevel::Function(name, args, _, expr) => allocator
.text("function")
.append(allocator.space())
.append(allocator.text("="))
.append(allocator.text(name.as_ref().to_string()))
.append(allocator.space())
.append(
pretty_comma_separated(
allocator,
&args.iter().map(|(x, _)| PrettySymbol::from(x)).collect(),
)
.parens(),
)
.append(allocator.space())
.append(expr.pretty(allocator)),
Statement::Print(_, _, var) => allocator
.text("print")
.append(allocator.space())
.append(allocator.text(var.as_ref().to_string())),
TopLevel::Statement(stmt) => stmt.pretty(allocator),
}
}
}
@@ -171,21 +145,27 @@ where
/// that the referenced data will always either be a constant or a
/// variable reference.
#[derive(Debug)]
pub enum Expression {
Atomic(ValueOrRef),
Cast(Location, Type, ValueOrRef),
Primitive(Location, Type, Primitive, Vec<ValueOrRef>),
pub enum Expression<Type> {
Atomic(ValueOrRef<Type>),
Cast(Location, Type, ValueOrRef<Type>),
Primitive(Location, Type, Primitive, Vec<ValueOrRef<Type>>),
Block(Location, Type, Vec<Expression<Type>>),
Print(Location, Variable),
Bind(Location, Variable, Type, Box<Expression<Type>>),
}
impl Expression {
impl<Type: Clone + TypeWithVoid> Expression<Type> {
/// Return a reference to the type of the expression, as inferred or recently
/// computed.
pub fn type_of(&self) -> &Type {
pub fn type_of(&self) -> Type {
match self {
Expression::Atomic(ValueOrRef::Ref(_, t, _)) => t,
Expression::Atomic(ValueOrRef::Value(_, t, _)) => t,
Expression::Cast(_, t, _) => t,
Expression::Primitive(_, t, _, _) => t,
Expression::Atomic(ValueOrRef::Ref(_, t, _)) => t.clone(),
Expression::Atomic(ValueOrRef::Value(_, t, _)) => t.clone(),
Expression::Cast(_, t, _) => t.clone(),
Expression::Primitive(_, t, _, _) => t.clone(),
Expression::Block(_, t, _) => t.clone(),
Expression::Print(_, _) => Type::void(),
Expression::Bind(_, _, _, _) => Type::void(),
}
}
@@ -196,14 +176,18 @@ impl Expression {
Expression::Atomic(ValueOrRef::Value(l, _, _)) => l,
Expression::Cast(l, _, _) => l,
Expression::Primitive(l, _, _, _) => l,
Expression::Block(l, _, _) => l,
Expression::Print(l, _) => l,
Expression::Bind(l, _, _, _) => l,
}
}
}
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b Expression
impl<'a, 'b, D, A, Type> Pretty<'a, D, A> for &'b Expression<Type>
where
A: 'a,
D: ?Sized + DocAllocator<'a, A>,
&'b Type: Pretty<'a, D, A>,
{
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
match self {
@@ -229,6 +213,35 @@ where
Expression::Primitive(_, _, op, exprs) => {
allocator.text(format!("!!{:?} with {} arguments!!", op, exprs.len()))
}
Expression::Block(_, _, exprs) => match exprs.split_last() {
None => allocator.text("()"),
Some((last, &[])) => last.pretty(allocator),
Some((last, start)) => {
let mut result = allocator.text("{").append(allocator.hardline());
for stmt in start.iter() {
result = result
.append(stmt.pretty(allocator))
.append(allocator.text(";"))
.append(allocator.hardline());
}
result
.append(last.pretty(allocator))
.append(allocator.hardline())
.append(allocator.text("}"))
}
},
Expression::Print(_, var) => allocator
.text("print")
.append(allocator.space())
.append(allocator.text(var.as_ref().to_string())),
Expression::Bind(_, var, _, expr) => allocator
.text(var.as_ref().to_string())
.append(allocator.space())
.append(allocator.text("="))
.append(allocator.space())
.append(expr.pretty(allocator)),
}
}
}
@@ -288,12 +301,12 @@ impl fmt::Display for Primitive {
/// at this level. Instead, expressions that take arguments take one
/// of these, which can only be a constant or a reference.
#[derive(Clone, Debug)]
pub enum ValueOrRef {
pub enum ValueOrRef<Type> {
Value(Location, Type, Value),
Ref(Location, Type, ArcIntern<String>),
}
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b ValueOrRef
impl<'a, 'b, D, A, Type> Pretty<'a, D, A> for &'b ValueOrRef<Type>
where
A: 'a,
D: ?Sized + DocAllocator<'a, A>,
@@ -306,8 +319,8 @@ where
}
}
impl From<ValueOrRef> for Expression {
fn from(value: ValueOrRef) -> Self {
impl<Type> From<ValueOrRef<Type>> for Expression<Type> {
fn from(value: ValueOrRef<Type>) -> Self {
Expression::Atomic(value)
}
}
@@ -434,3 +447,121 @@ impl fmt::Display for Type {
}
}
}
impl From<PrimitiveType> for Type {
fn from(value: PrimitiveType) -> Self {
Type::Primitive(value)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum TypeOrVar {
Primitive(PrimitiveType),
Variable(Location, ArcIntern<String>),
Function(Vec<TypeOrVar>, Box<TypeOrVar>),
}
impl<'a, 'b, D, A> Pretty<'a, D, A> for &'b TypeOrVar
where
A: 'a,
D: ?Sized + DocAllocator<'a, A>,
{
fn pretty(self, allocator: &'a D) -> pretty::DocBuilder<'a, D, A> {
match self {
TypeOrVar::Primitive(x) => allocator.text(format!("{}", x)),
TypeOrVar::Variable(_, x) => allocator.text(x.to_string()),
TypeOrVar::Function(args, rettype) => {
pretty_comma_separated(allocator, &args.iter().collect())
.parens()
.append(allocator.space())
.append(allocator.text("->"))
.append(allocator.space())
.append(rettype.pretty(allocator))
}
}
}
}
impl fmt::Display for TypeOrVar {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
TypeOrVar::Primitive(x) => x.fmt(f),
TypeOrVar::Variable(_, v) => write!(f, "{}", v),
TypeOrVar::Function(args, rettype) => {
write!(f, "<function:")?;
match args.split_last() {
None => write!(f, "()")?,
Some((single, &[])) => {
write!(f, "({})", single)?;
}
Some((last_one, rest)) => {
write!(f, "(")?;
for arg in rest.iter() {
write!(f, "{}, ", arg);
}
write!(f, "{})", last_one)?;
}
}
write!(f, "->")?;
rettype.fmt(f)?;
write!(f, ">")
}
}
}
}
impl TypeOrVar {
/// Generate a fresh type variable that is different from all previous type variables.
///
/// This type variable is guaranteed to be unique across the process lifetime. Overuse
/// of this function could potentially cause overflow problems, but you're going to have
/// to try really hard (like, 2^64 times) to make that happen. The location bound to
/// this address will be purely manufactured; if you want to specify a location, use
/// [`TypeOrVar::new_located`].
pub fn new() -> Self {
Self::new_located(Location::manufactured())
}
/// Generate a fresh type variable that is different from all previous type variables.
///
/// This type variable is guaranteed to be unique across the process lifetime. Overuse
/// of this function could potentially cause overflow problems, but you're going to have
/// to try really hard (like, 2^64 times) to make that happen.
pub fn new_located(loc: Location) -> Self {
TypeOrVar::Variable(loc, gensym("t"))
}
}
trait TypeWithVoid {
fn void() -> Self;
}
impl TypeWithVoid for Type {
fn void() -> Self {
Type::Primitive(PrimitiveType::Void)
}
}
impl TypeWithVoid for TypeOrVar {
fn void() -> Self {
TypeOrVar::Primitive(PrimitiveType::Void)
}
}
//impl From<Type> for TypeOrVar {
// fn from(value: Type) -> Self {
// TypeOrVar::Type(value)
// }
//}
impl<T: Into<Type>> From<T> for TypeOrVar {
fn from(value: T) -> Self {
match value.into() {
Type::Primitive(p) => TypeOrVar::Primitive(p),
Type::Function(args, ret) => TypeOrVar::Function(
args.into_iter().map(Into::into).collect(),
Box::new((*ret).into()),
),
}
}
}