Start isolating names into their own thing.

This commit is contained in:
2024-04-29 21:38:17 -07:00
parent 52d5c9252b
commit 4c2850427a
12 changed files with 176 additions and 111 deletions

8
src/lambda_lift.rs Normal file
View File

@@ -0,0 +1,8 @@
use crate::syntax::{Expression, Name};
use std::collections::{HashSet, HashMap};
impl Expression {
fn free_variables(&self) -> HashSet<Name> {
unimplemented!()
}
}

View File

@@ -66,6 +66,7 @@ pub mod eval;
#[cfg(test)] #[cfg(test)]
mod examples; mod examples;
pub mod ir; pub mod ir;
pub mod lambda_lift;
pub mod syntax; pub mod syntax;
pub mod type_infer; pub mod type_infer;
pub mod util; pub mod util;

View File

@@ -134,8 +134,8 @@ impl REPL {
// if this is a variable binding, and we've never defined this variable before, // if this is a variable binding, and we've never defined this variable before,
// we should tell cranelift about it. this is optimistic; if we fail to compile, // we should tell cranelift about it. this is optimistic; if we fail to compile,
// then we won't use this definition until someone tries again. // then we won't use this definition until someone tries again.
if !self.variable_binding_sites.contains_key(&name.name) { if !self.variable_binding_sites.contains_key(&name.current_name().to_string()) {
self.jitter.define_string(&name.name)?; self.jitter.define_string(name.current_name())?;
self.jitter self.jitter
.define_variable(name.to_string(), ConstantType::U64)?; .define_variable(name.to_string(), ConstantType::U64)?;
} }
@@ -149,7 +149,7 @@ impl REPL {
loc.clone(), loc.clone(),
crate::syntax::Name::manufactured("print"), crate::syntax::Name::manufactured("print"),
)), )),
vec![Expression::Reference(loc.clone(), name.name)], vec![Expression::Reference(name.clone())],
)), )),
], ],
} }

View File

@@ -31,6 +31,7 @@ pub mod arbitrary;
mod ast; mod ast;
pub mod eval; pub mod eval;
mod location; mod location;
mod name;
mod tokens; mod tokens;
lalrpop_mod!( lalrpop_mod!(
#[allow(clippy::just_underscores_and_digits, clippy::clone_on_copy)] #[allow(clippy::just_underscores_and_digits, clippy::clone_on_copy)]
@@ -44,6 +45,7 @@ mod validate;
use crate::syntax::arbitrary::GenerationEnvironment; use crate::syntax::arbitrary::GenerationEnvironment;
pub use crate::syntax::ast::*; pub use crate::syntax::ast::*;
pub use crate::syntax::location::Location; pub use crate::syntax::location::Location;
pub use crate::syntax::name::Name;
pub use crate::syntax::parser::{ProgramParser, TopLevelParser}; pub use crate::syntax::parser::{ProgramParser, TopLevelParser};
pub use crate::syntax::tokens::{LexerError, Token}; pub use crate::syntax::tokens::{LexerError, Token};
use lalrpop_util::ParseError; use lalrpop_util::ParseError;

View File

@@ -1,4 +1,5 @@
use crate::syntax::ast::{ConstantType, Expression, Name, Program, TopLevel, Value}; use crate::syntax::ast::{ConstantType, Expression, Program, TopLevel, Value};
use crate::syntax::name::Name;
use crate::syntax::location::Location; use crate::syntax::location::Location;
use proptest::sample::select; use proptest::sample::select;
use proptest::{ use proptest::{
@@ -88,10 +89,7 @@ impl Arbitrary for Program {
Location::manufactured(), Location::manufactured(),
Name::manufactured("print"), Name::manufactured("print"),
)), )),
vec![Expression::Reference( vec![Expression::Reference(n.clone())],
Location::manufactured(),
n.to_string(),
)],
))) )))
}); });
items.push(Union::new(printers).boxed()); items.push(Union::new(printers).boxed());
@@ -168,12 +166,7 @@ impl Arbitrary for Expression {
} else { } else {
let mut strats = bound_variables_of_type let mut strats = bound_variables_of_type
.drain(..) .drain(..)
.map(|x| { .map(|x| { Just(Expression::Reference(x.clone())).boxed()
Just(Expression::Reference(
Location::manufactured(),
x.name.clone(),
))
.boxed()
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
strats.push(value_strategy); strats.push(value_strategy);

View File

@@ -1,10 +1,6 @@
use std::fmt; use crate::syntax::name::Name;
use std::hash::Hash;
use internment::ArcIntern;
pub use crate::syntax::tokens::ConstantType;
use crate::syntax::Location; use crate::syntax::Location;
pub use crate::syntax::tokens::ConstantType;
/// A structure represented a parsed program. /// A structure represented a parsed program.
/// ///
@@ -30,56 +26,6 @@ pub enum TopLevel {
Structure(Location, Name, Vec<(Name, Type)>), Structure(Location, Name, Vec<(Name, Type)>),
} }
/// A Name.
///
/// This is basically a string, but annotated with the place the string
/// is in the source file.
#[derive(Clone, Debug)]
pub struct Name {
pub name: String,
pub location: Location,
}
impl Name {
pub fn new<S: ToString>(n: S, location: Location) -> Name {
Name {
name: n.to_string(),
location,
}
}
pub fn manufactured<S: ToString>(n: S) -> Name {
Name {
name: n.to_string(),
location: Location::manufactured(),
}
}
pub fn intern(self) -> ArcIntern<String> {
ArcIntern::new(self.name)
}
}
impl PartialEq for Name {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
impl Eq for Name {}
impl Hash for Name {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name.hash(state)
}
}
impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.name.fmt(f)
}
}
/// An expression in the underlying syntax. /// An expression in the underlying syntax.
/// ///
/// Like statements, these expressions are guaranteed to have been /// Like statements, these expressions are guaranteed to have been
@@ -90,7 +36,7 @@ impl fmt::Display for Name {
pub enum Expression { pub enum Expression {
Value(Location, Value), Value(Location, Value),
Constructor(Location, Name, Vec<(Name, Expression)>), Constructor(Location, Name, Vec<(Name, Expression)>),
Reference(Location, String), Reference(Name),
FieldRef(Location, Box<Expression>, Name), FieldRef(Location, Box<Expression>, Name),
Cast(Location, String, Box<Expression>), Cast(Location, String, Box<Expression>),
Primitive(Location, Name), Primitive(Location, Name),
@@ -130,8 +76,8 @@ impl PartialEq for Expression {
Expression::Constructor(_, name2, fields2) => name1 == name2 && fields1 == fields2, Expression::Constructor(_, name2, fields2) => name1 == name2 && fields1 == fields2,
_ => false, _ => false,
}, },
Expression::Reference(_, var1) => match other { Expression::Reference(var1) => match other {
Expression::Reference(_, var2) => var1 == var2, Expression::Reference(var2) => var1 == var2,
_ => false, _ => false,
}, },
Expression::FieldRef(_, exp1, field1) => match other { Expression::FieldRef(_, exp1, field1) => match other {
@@ -174,7 +120,7 @@ impl Expression {
match self { match self {
Expression::Value(loc, _) => loc, Expression::Value(loc, _) => loc,
Expression::Constructor(loc, _, _) => loc, Expression::Constructor(loc, _, _) => loc,
Expression::Reference(loc, _) => loc, Expression::Reference(n) => n.location(),
Expression::FieldRef(loc, _, _) => loc, Expression::FieldRef(loc, _, _) => loc,
Expression::Cast(loc, _, _) => loc, Expression::Cast(loc, _, _) => loc,
Expression::Primitive(loc, _) => loc, Expression::Primitive(loc, _) => loc,

View File

@@ -1,5 +1,5 @@
use crate::eval::{EvalError, PrimitiveType, Value}; use crate::eval::{EvalError, PrimitiveType, Value};
use crate::syntax::{ConstantType, Expression, Name, Program, TopLevel}; use crate::syntax::{ConstantType, Expression, Program, TopLevel};
use crate::util::scoped_map::ScopedMap; use crate::util::scoped_map::ScopedMap;
use internment::ArcIntern; use internment::ArcIntern;
use std::collections::HashMap; use std::collections::HashMap;
@@ -69,9 +69,9 @@ impl Expression {
Ok(Value::Structure(Some(on.clone().intern()), map)) Ok(Value::Structure(Some(on.clone().intern()), map))
} }
Expression::Reference(loc, n) => env Expression::Reference(n) => env
.get(&ArcIntern::new(n.clone())) .get(n.current_interned())
.ok_or_else(|| EvalError::LookupFailed(loc.clone(), n.clone())) .ok_or_else(|| EvalError::LookupFailed(n.location().clone(), n.current_name().to_string()))
.cloned(), .cloned(),
Expression::FieldRef(loc, expr, field) => { Expression::FieldRef(loc, expr, field) => {
@@ -102,7 +102,7 @@ impl Expression {
Ok(target_type.safe_cast(&value)?) Ok(target_type.safe_cast(&value)?)
} }
Expression::Primitive(_, op) => Ok(Value::primitive(op.name.clone())), Expression::Primitive(_, op) => Ok(Value::primitive(op.original_name().to_string())),
Expression::Call(loc, fun, args) => { Expression::Call(loc, fun, args) => {
let function = fun.eval(stdout, env)?; let function = fun.eval(stdout, env)?;
@@ -129,8 +129,8 @@ impl Expression {
} }
Value::Primitive(name) if name == "print" => { Value::Primitive(name) if name == "print" => {
if let [Expression::Reference(_, name)] = &args[..] { if let [Expression::Reference(name)] = &args[..] {
let value = Expression::Reference(loc.clone(), name.clone()) let value = Expression::Reference(name.clone())
.eval(stdout, env)?; .eval(stdout, env)?;
let value = match value { let value = match value {
Value::Number(x) => Value::U64(x), Value::Number(x) => Value::U64(x),
@@ -178,12 +178,12 @@ impl Expression {
Expression::Function(_, name, arg_names, _, body) => { Expression::Function(_, name, arg_names, _, body) => {
let result = Value::Closure( let result = Value::Closure(
name.clone().map(Name::intern), name.as_ref().map(|n| n.current_interned().clone()),
env.clone(), env.clone(),
arg_names arg_names
.iter() .iter()
.cloned() .cloned()
.map(|(x, _)| Name::intern(x)) .map(|(x, _)| x.current_interned().clone())
.collect(), .collect(),
*body.clone(), *body.clone(),
); );

113
src/syntax/name.rs Normal file
View File

@@ -0,0 +1,113 @@
use crate::syntax::Location;
use internment::ArcIntern;
use std::fmt;
use std::hash::Hash;
/// The name of a thing in the source language.
///
/// In many ways, you can treat this like a string, but it's a very tricky
/// string in a couple of ways:
///
/// First, it's a string associated with a particular location in the source
/// file, and you can find out what that source location is relatively easily.
///
/// Second, it's a name that retains something of its identity across renaming,
/// so that you can keep track of what a variables original name was, as well as
/// what it's new name is if it's been renamed.
///
/// Finally, when it comes to equality tests, comparisons, and hashing, `Name`
/// uses *only* the new name, if the variable has been renamed, or the original
/// name, if it has not been renamed. It never uses the location. This allows
/// relatively fast hashing and searching for things like binding sites, as the
/// value of the binding `Name` will be equal to the bound `Name`, even though
/// they occur at different locations.
#[derive(Clone, Debug)]
pub struct Name {
name: ArcIntern<String>,
rename: Option<ArcIntern<String>>,
location: Location,
}
impl Name {
/// Create a new name at the given location.
///
/// This creates an "original" name, which has not been renamed, at the
/// given location.
pub fn new<S: ToString>(n: S, location: Location) -> Name {
Name {
name: ArcIntern::new(n.to_string()),
rename: None,
location,
}
}
/// Create a new name with no location information.
///
/// This creates an "original" name, which has not been renamed, at the
/// given location. You should always prefer to use [`Location::new`] if
/// there is any possible way to get it, because that will be more
/// helpful to our users.
pub fn manufactured<S: ToString>(n: S) -> Name {
Name {
name: ArcIntern::new(n.to_string()),
rename: None,
location: Location::manufactured(),
}
}
/// Returns a reference to the original name of the variable.
///
/// Regardless of whether or not the function has been renamed, this will
/// return whatever name this variable started with.
pub fn original_name(&self) -> &str {
self.name.as_str()
}
/// Returns a reference to the current name of the variable.
///
/// If the variable has been renamed, it will return that, otherwise we'll
/// return the current name.
pub fn current_name(&self) -> &str {
self.rename.as_ref().map(|x| x.as_str()).unwrap_or_else(|| self.name.as_str())
}
/// Returns the current name of the variable as an interned string.
pub fn current_interned(&self) -> &ArcIntern<String> {
self.rename.as_ref().unwrap_or(&self.name)
}
/// Return the location of this name.
pub fn location(&self) -> &Location {
&self.location
}
/// Rename this variable to the given value
pub fn rename(&mut self, new_name: &ArcIntern<String>) {
self.rename = Some(new_name.clone());
}
pub fn intern(&self) -> ArcIntern<String> {
self.current_interned().clone()
}
}
impl PartialEq for Name {
fn eq(&self, other: &Self) -> bool {
self.current_interned() == other.current_interned()
}
}
impl Eq for Name {}
impl Hash for Name {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.current_interned().hash(state)
}
}
impl fmt::Display for Name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.current_name().fmt(f)
}
}

View File

@@ -9,7 +9,8 @@
//! eventually want to leave lalrpop behind.) //! eventually want to leave lalrpop behind.)
//! //!
use crate::syntax::{Location, ParserError}; use crate::syntax::{Location, ParserError};
use crate::syntax::ast::{Program,TopLevel,Expression,Value,Name,Type}; use crate::syntax::ast::{Program,TopLevel,Expression,Value,Type};
use crate::syntax::name::Name;
use crate::syntax::tokens::{ConstantType, Token}; use crate::syntax::tokens::{ConstantType, Token};
use internment::ArcIntern; use internment::ArcIntern;
@@ -241,7 +242,7 @@ FieldExpression: Expression = {
// they cannot be further divided into parts // they cannot be further divided into parts
AtomicExpression: Expression = { AtomicExpression: Expression = {
// just a variable reference // just a variable reference
<l: @L> <v:"<var>"> <end: @L> => Expression::Reference(Location::new(file_idx, l..end), v.to_string()), <l: @L> <v:"<var>"> <end: @L> => Expression::Reference(Name::new(v.to_string(), Location::new(file_idx, l..end))),
// just a number // just a number
<l: @L> <n:"<num>"> <end: @L> => Expression::Value(Location::new(file_idx, l..end), Value::Number(n.0, n.1, n.2)), <l: @L> <n:"<num>"> <end: @L> => Expression::Value(Location::new(file_idx, l..end), Value::Number(n.0, n.1, n.2)),
// this expression could actually be a block! // this expression could actually be a block!

View File

@@ -67,7 +67,7 @@ impl Expression {
.nest(2) .nest(2)
.braces(), .braces(),
), ),
Expression::Reference(_, var) => allocator.text(var.to_string()), Expression::Reference(var) => allocator.text(var.to_string()),
Expression::FieldRef(_, val, field) => val Expression::FieldRef(_, val, field) => val
.pretty(allocator) .pretty(allocator)
.append(allocator.text(".")) .append(allocator.text("."))
@@ -76,7 +76,7 @@ impl Expression {
.text(t.clone()) .text(t.clone())
.angles() .angles()
.append(e.pretty(allocator)), .append(e.pretty(allocator)),
Expression::Primitive(_, op) => allocator.text(op.name.clone()), Expression::Primitive(_, op) => allocator.text(op.original_name().to_string()),
Expression::Call(_, fun, args) => { Expression::Call(_, fun, args) => {
let args = args.iter().map(|x| x.pretty(allocator)); let args = args.iter().map(|x| x.pretty(allocator));
let comma_sepped_args = allocator.intersperse(args, allocator.text(",")); let comma_sepped_args = allocator.intersperse(args, allocator.text(","));

View File

@@ -141,9 +141,9 @@ impl Expression {
(errors, warnings) (errors, warnings)
} }
Expression::Reference(_, var) if variable_map.contains_key(var) => (vec![], vec![]), Expression::Reference(var) if variable_map.contains_key(&var.original_name().to_string()) => (vec![], vec![]),
Expression::Reference(loc, var) => ( Expression::Reference(var) => (
vec![Error::UnboundVariable(loc.clone(), var.clone())], vec![Error::UnboundVariable(var.location().clone(), var.original_name().to_string())],
vec![], vec![],
), ),
Expression::FieldRef(_, exp, _) => exp.validate(variable_map), Expression::FieldRef(_, exp, _) => exp.validate(variable_map),
@@ -187,7 +187,7 @@ impl Expression {
// immediately check the expression, and go from there. // immediately check the expression, and go from there.
let (errors, mut warnings) = val.validate(variable_map); let (errors, mut warnings) = val.validate(variable_map);
if let Some(original_binding_site) = variable_map.get(&var.name) { if let Some(original_binding_site) = variable_map.get(&var.original_name().to_string()) {
warnings.push(Warning::ShadowedVariable( warnings.push(Warning::ShadowedVariable(
original_binding_site.clone(), original_binding_site.clone(),
loc.clone(), loc.clone(),
@@ -201,11 +201,11 @@ impl Expression {
} }
Expression::Function(_, name, arguments, _, body) => { Expression::Function(_, name, arguments, _, body) => {
if let Some(name) = name { if let Some(name) = name {
variable_map.insert(name.name.clone(), name.location.clone()); variable_map.insert(name.original_name().to_string(), name.location().clone());
} }
variable_map.new_scope(); variable_map.new_scope();
for (arg, _) in arguments.iter() { for (arg, _) in arguments.iter() {
variable_map.insert(arg.name.clone(), arg.location.clone()); variable_map.insert(arg.original_name().to_string(), arg.location().clone());
} }
let result = body.validate(variable_map); let result = body.validate(variable_map);
variable_map.release_scope(); variable_map.release_scope();

View File

@@ -191,20 +191,21 @@ impl InferenceEngine {
} }
} }
syntax::Expression::Reference(loc, name) => { syntax::Expression::Reference(mut name) => {
let iname = ArcIntern::new(name); if let Some(rename) = renames.get(name.current_interned()) {
let final_name = renames.get(&iname).cloned().unwrap_or(iname); name.rename(rename);
}
let result_type = self let result_type = self
.variable_types .variable_types
.get(&final_name) .get(name.current_interned())
.cloned() .cloned()
.expect("variable bound before use"); .expect("variable bound before use");
let expression = ir::Expression::Atomic(ir::ValueOrRef::Ref( let expression = ir::Expression::Atomic(ir::ValueOrRef::Ref(
loc, name.location().clone(),
result_type.clone(), result_type.clone(),
final_name.clone(), name.current_interned().clone(),
)); ));
let free_variables = HashSet::from([final_name]); let free_variables = HashSet::from([name.current_interned().clone()]);
ExpressionInfo { ExpressionInfo {
expression, expression,
@@ -260,7 +261,7 @@ impl InferenceEngine {
} }
syntax::Expression::Primitive(loc, name) => { syntax::Expression::Primitive(loc, name) => {
let primop = ir::Primitive::from_str(&name.name).expect("valid primitive"); let primop = ir::Primitive::from_str(&name.current_name()).expect("valid primitive");
match primop { match primop {
ir::Primitive::Plus | ir::Primitive::Times | ir::Primitive::Divide => { ir::Primitive::Plus | ir::Primitive::Times | ir::Primitive::Divide => {
@@ -404,12 +405,12 @@ impl InferenceEngine {
expr_info expr_info
} }
syntax::Expression::Function(_, name, args, _, expr) => { syntax::Expression::Function(loc, name, args, _, expr) => {
// First, at some point we're going to want to know a location for this function, // First, at some point we're going to want to know a location for this function,
// which should either be the name if we have one, or the body if we don't. // which should either be the name if we have one, or the body if we don't.
let function_location = match name { let function_location = match name {
None => expr.location().clone(), None => expr.location().clone(),
Some(ref name) => name.location.clone(), Some(ref name) => loc,
}; };
// Next, let us figure out what we're going to name this function. If the user // Next, let us figure out what we're going to name this function. If the user
// didn't provide one, we'll just call it "function:<something>" for them. (We'll // didn't provide one, we'll just call it "function:<something>" for them. (We'll
@@ -440,7 +441,7 @@ impl InferenceEngine {
.map(|(name, mut declared_type)| { .map(|(name, mut declared_type)| {
let new_type = ir::TypeOrVar::new(); let new_type = ir::TypeOrVar::new();
self.constraints.push(Constraint::IsSomething( self.constraints.push(Constraint::IsSomething(
name.location.clone(), name.location().clone(),
new_type.clone(), new_type.clone(),
)); ));
let new_name = self.finalize_name(renames, name.clone()); let new_name = self.finalize_name(renames, name.clone());
@@ -450,7 +451,7 @@ impl InferenceEngine {
if let Some(declared_type) = declared_type.take() { if let Some(declared_type) = declared_type.take() {
let declared_type = self.convert_type(declared_type); let declared_type = self.convert_type(declared_type);
self.constraints.push(Constraint::Equivalent( self.constraints.push(Constraint::Equivalent(
name.location.clone(), name.location().clone(),
new_type.clone(), new_type.clone(),
declared_type, declared_type,
)); ));
@@ -495,11 +496,11 @@ impl InferenceEngine {
fn convert_type(&mut self, ty: syntax::Type) -> ir::TypeOrVar { fn convert_type(&mut self, ty: syntax::Type) -> ir::TypeOrVar {
match ty { match ty {
syntax::Type::Named(x) => match PrimitiveType::from_str(x.name.as_str()) { syntax::Type::Named(x) => match PrimitiveType::from_str(x.current_name()) {
Err(_) => { Err(_) => {
let retval = ir::TypeOrVar::new_located(x.location.clone()); let retval = ir::TypeOrVar::new_located(x.location().clone());
self.constraints.push(Constraint::NamedTypeIs( self.constraints.push(Constraint::NamedTypeIs(
x.location.clone(), x.location().clone(),
x.intern(), x.intern(),
retval.clone(), retval.clone(),
)); ));
@@ -529,10 +530,10 @@ impl InferenceEngine {
) -> ArcIntern<String> { ) -> ArcIntern<String> {
if self if self
.variable_types .variable_types
.contains_key(&ArcIntern::new(name.name.clone())) .contains_key(name.current_interned())
{ {
let new_name = ir::gensym(&name.name); let new_name = ir::gensym(&name.original_name());
renames.insert(ArcIntern::new(name.name.to_string()), new_name.clone()); renames.insert(name.current_interned().clone(), new_name.clone());
new_name new_name
} else { } else {
ArcIntern::new(name.to_string()) ArcIntern::new(name.to_string())