checkpoint; builds again
This commit is contained in:
325
src/ir/ast.rs
325
src/ir/ast.rs
@@ -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()),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user