λ Support functions! #5
8
src/lambda_lift.rs
Normal file
8
src/lambda_lift.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
use crate::syntax::{Expression, Name};
|
||||
use std::collections::{HashSet, HashMap};
|
||||
|
||||
impl Expression {
|
||||
fn free_variables(&self) -> HashSet<Name> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,7 @@ pub mod eval;
|
||||
#[cfg(test)]
|
||||
mod examples;
|
||||
pub mod ir;
|
||||
pub mod lambda_lift;
|
||||
pub mod syntax;
|
||||
pub mod type_infer;
|
||||
pub mod util;
|
||||
|
||||
@@ -134,8 +134,8 @@ impl REPL {
|
||||
// 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,
|
||||
// then we won't use this definition until someone tries again.
|
||||
if !self.variable_binding_sites.contains_key(&name.name) {
|
||||
self.jitter.define_string(&name.name)?;
|
||||
if !self.variable_binding_sites.contains_key(&name.current_name().to_string()) {
|
||||
self.jitter.define_string(name.current_name())?;
|
||||
self.jitter
|
||||
.define_variable(name.to_string(), ConstantType::U64)?;
|
||||
}
|
||||
@@ -149,7 +149,7 @@ impl REPL {
|
||||
loc.clone(),
|
||||
crate::syntax::Name::manufactured("print"),
|
||||
)),
|
||||
vec![Expression::Reference(loc.clone(), name.name)],
|
||||
vec![Expression::Reference(name.clone())],
|
||||
)),
|
||||
],
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ pub mod arbitrary;
|
||||
mod ast;
|
||||
pub mod eval;
|
||||
mod location;
|
||||
mod name;
|
||||
mod tokens;
|
||||
lalrpop_mod!(
|
||||
#[allow(clippy::just_underscores_and_digits, clippy::clone_on_copy)]
|
||||
@@ -44,6 +45,7 @@ mod validate;
|
||||
use crate::syntax::arbitrary::GenerationEnvironment;
|
||||
pub use crate::syntax::ast::*;
|
||||
pub use crate::syntax::location::Location;
|
||||
pub use crate::syntax::name::Name;
|
||||
pub use crate::syntax::parser::{ProgramParser, TopLevelParser};
|
||||
pub use crate::syntax::tokens::{LexerError, Token};
|
||||
use lalrpop_util::ParseError;
|
||||
|
||||
@@ -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 proptest::sample::select;
|
||||
use proptest::{
|
||||
@@ -88,10 +89,7 @@ impl Arbitrary for Program {
|
||||
Location::manufactured(),
|
||||
Name::manufactured("print"),
|
||||
)),
|
||||
vec![Expression::Reference(
|
||||
Location::manufactured(),
|
||||
n.to_string(),
|
||||
)],
|
||||
vec![Expression::Reference(n.clone())],
|
||||
)))
|
||||
});
|
||||
items.push(Union::new(printers).boxed());
|
||||
@@ -168,12 +166,7 @@ impl Arbitrary for Expression {
|
||||
} else {
|
||||
let mut strats = bound_variables_of_type
|
||||
.drain(..)
|
||||
.map(|x| {
|
||||
Just(Expression::Reference(
|
||||
Location::manufactured(),
|
||||
x.name.clone(),
|
||||
))
|
||||
.boxed()
|
||||
.map(|x| { Just(Expression::Reference(x.clone())).boxed()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
strats.push(value_strategy);
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
use std::fmt;
|
||||
use std::hash::Hash;
|
||||
|
||||
use internment::ArcIntern;
|
||||
|
||||
pub use crate::syntax::tokens::ConstantType;
|
||||
use crate::syntax::name::Name;
|
||||
use crate::syntax::Location;
|
||||
pub use crate::syntax::tokens::ConstantType;
|
||||
|
||||
/// A structure represented a parsed program.
|
||||
///
|
||||
@@ -30,56 +26,6 @@ pub enum TopLevel {
|
||||
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.
|
||||
///
|
||||
/// Like statements, these expressions are guaranteed to have been
|
||||
@@ -90,7 +36,7 @@ impl fmt::Display for Name {
|
||||
pub enum Expression {
|
||||
Value(Location, Value),
|
||||
Constructor(Location, Name, Vec<(Name, Expression)>),
|
||||
Reference(Location, String),
|
||||
Reference(Name),
|
||||
FieldRef(Location, Box<Expression>, Name),
|
||||
Cast(Location, String, Box<Expression>),
|
||||
Primitive(Location, Name),
|
||||
@@ -130,8 +76,8 @@ impl PartialEq for Expression {
|
||||
Expression::Constructor(_, name2, fields2) => name1 == name2 && fields1 == fields2,
|
||||
_ => false,
|
||||
},
|
||||
Expression::Reference(_, var1) => match other {
|
||||
Expression::Reference(_, var2) => var1 == var2,
|
||||
Expression::Reference(var1) => match other {
|
||||
Expression::Reference(var2) => var1 == var2,
|
||||
_ => false,
|
||||
},
|
||||
Expression::FieldRef(_, exp1, field1) => match other {
|
||||
@@ -174,7 +120,7 @@ impl Expression {
|
||||
match self {
|
||||
Expression::Value(loc, _) => loc,
|
||||
Expression::Constructor(loc, _, _) => loc,
|
||||
Expression::Reference(loc, _) => loc,
|
||||
Expression::Reference(n) => n.location(),
|
||||
Expression::FieldRef(loc, _, _) => loc,
|
||||
Expression::Cast(loc, _, _) => loc,
|
||||
Expression::Primitive(loc, _) => loc,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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 internment::ArcIntern;
|
||||
use std::collections::HashMap;
|
||||
@@ -69,9 +69,9 @@ impl Expression {
|
||||
Ok(Value::Structure(Some(on.clone().intern()), map))
|
||||
}
|
||||
|
||||
Expression::Reference(loc, n) => env
|
||||
.get(&ArcIntern::new(n.clone()))
|
||||
.ok_or_else(|| EvalError::LookupFailed(loc.clone(), n.clone()))
|
||||
Expression::Reference(n) => env
|
||||
.get(n.current_interned())
|
||||
.ok_or_else(|| EvalError::LookupFailed(n.location().clone(), n.current_name().to_string()))
|
||||
.cloned(),
|
||||
|
||||
Expression::FieldRef(loc, expr, field) => {
|
||||
@@ -102,7 +102,7 @@ impl Expression {
|
||||
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) => {
|
||||
let function = fun.eval(stdout, env)?;
|
||||
@@ -129,8 +129,8 @@ impl Expression {
|
||||
}
|
||||
|
||||
Value::Primitive(name) if name == "print" => {
|
||||
if let [Expression::Reference(_, name)] = &args[..] {
|
||||
let value = Expression::Reference(loc.clone(), name.clone())
|
||||
if let [Expression::Reference(name)] = &args[..] {
|
||||
let value = Expression::Reference(name.clone())
|
||||
.eval(stdout, env)?;
|
||||
let value = match value {
|
||||
Value::Number(x) => Value::U64(x),
|
||||
@@ -178,12 +178,12 @@ impl Expression {
|
||||
|
||||
Expression::Function(_, name, arg_names, _, body) => {
|
||||
let result = Value::Closure(
|
||||
name.clone().map(Name::intern),
|
||||
name.as_ref().map(|n| n.current_interned().clone()),
|
||||
env.clone(),
|
||||
arg_names
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|(x, _)| Name::intern(x))
|
||||
.map(|(x, _)| x.current_interned().clone())
|
||||
.collect(),
|
||||
*body.clone(),
|
||||
);
|
||||
|
||||
113
src/syntax/name.rs
Normal file
113
src/syntax/name.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
//! eventually want to leave lalrpop behind.)
|
||||
//!
|
||||
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 internment::ArcIntern;
|
||||
|
||||
@@ -241,7 +242,7 @@ FieldExpression: Expression = {
|
||||
// they cannot be further divided into parts
|
||||
AtomicExpression: Expression = {
|
||||
// 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
|
||||
<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!
|
||||
|
||||
@@ -67,7 +67,7 @@ impl Expression {
|
||||
.nest(2)
|
||||
.braces(),
|
||||
),
|
||||
Expression::Reference(_, var) => allocator.text(var.to_string()),
|
||||
Expression::Reference(var) => allocator.text(var.to_string()),
|
||||
Expression::FieldRef(_, val, field) => val
|
||||
.pretty(allocator)
|
||||
.append(allocator.text("."))
|
||||
@@ -76,7 +76,7 @@ impl Expression {
|
||||
.text(t.clone())
|
||||
.angles()
|
||||
.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) => {
|
||||
let args = args.iter().map(|x| x.pretty(allocator));
|
||||
let comma_sepped_args = allocator.intersperse(args, allocator.text(","));
|
||||
|
||||
@@ -141,9 +141,9 @@ impl Expression {
|
||||
|
||||
(errors, warnings)
|
||||
}
|
||||
Expression::Reference(_, var) if variable_map.contains_key(var) => (vec![], vec![]),
|
||||
Expression::Reference(loc, var) => (
|
||||
vec![Error::UnboundVariable(loc.clone(), var.clone())],
|
||||
Expression::Reference(var) if variable_map.contains_key(&var.original_name().to_string()) => (vec![], vec![]),
|
||||
Expression::Reference(var) => (
|
||||
vec![Error::UnboundVariable(var.location().clone(), var.original_name().to_string())],
|
||||
vec![],
|
||||
),
|
||||
Expression::FieldRef(_, exp, _) => exp.validate(variable_map),
|
||||
@@ -187,7 +187,7 @@ impl Expression {
|
||||
// immediately check the expression, and go from there.
|
||||
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(
|
||||
original_binding_site.clone(),
|
||||
loc.clone(),
|
||||
@@ -201,11 +201,11 @@ impl Expression {
|
||||
}
|
||||
Expression::Function(_, name, arguments, _, body) => {
|
||||
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();
|
||||
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);
|
||||
variable_map.release_scope();
|
||||
|
||||
@@ -191,20 +191,21 @@ impl InferenceEngine {
|
||||
}
|
||||
}
|
||||
|
||||
syntax::Expression::Reference(loc, name) => {
|
||||
let iname = ArcIntern::new(name);
|
||||
let final_name = renames.get(&iname).cloned().unwrap_or(iname);
|
||||
syntax::Expression::Reference(mut name) => {
|
||||
if let Some(rename) = renames.get(name.current_interned()) {
|
||||
name.rename(rename);
|
||||
}
|
||||
let result_type = self
|
||||
.variable_types
|
||||
.get(&final_name)
|
||||
.get(name.current_interned())
|
||||
.cloned()
|
||||
.expect("variable bound before use");
|
||||
let expression = ir::Expression::Atomic(ir::ValueOrRef::Ref(
|
||||
loc,
|
||||
name.location().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 {
|
||||
expression,
|
||||
@@ -260,7 +261,7 @@ impl InferenceEngine {
|
||||
}
|
||||
|
||||
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 {
|
||||
ir::Primitive::Plus | ir::Primitive::Times | ir::Primitive::Divide => {
|
||||
@@ -404,12 +405,12 @@ impl InferenceEngine {
|
||||
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,
|
||||
// which should either be the name if we have one, or the body if we don't.
|
||||
let function_location = match name {
|
||||
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
|
||||
// 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)| {
|
||||
let new_type = ir::TypeOrVar::new();
|
||||
self.constraints.push(Constraint::IsSomething(
|
||||
name.location.clone(),
|
||||
name.location().clone(),
|
||||
new_type.clone(),
|
||||
));
|
||||
let new_name = self.finalize_name(renames, name.clone());
|
||||
@@ -450,7 +451,7 @@ impl InferenceEngine {
|
||||
if let Some(declared_type) = declared_type.take() {
|
||||
let declared_type = self.convert_type(declared_type);
|
||||
self.constraints.push(Constraint::Equivalent(
|
||||
name.location.clone(),
|
||||
name.location().clone(),
|
||||
new_type.clone(),
|
||||
declared_type,
|
||||
));
|
||||
@@ -495,11 +496,11 @@ impl InferenceEngine {
|
||||
|
||||
fn convert_type(&mut self, ty: syntax::Type) -> ir::TypeOrVar {
|
||||
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(_) => {
|
||||
let retval = ir::TypeOrVar::new_located(x.location.clone());
|
||||
let retval = ir::TypeOrVar::new_located(x.location().clone());
|
||||
self.constraints.push(Constraint::NamedTypeIs(
|
||||
x.location.clone(),
|
||||
x.location().clone(),
|
||||
x.intern(),
|
||||
retval.clone(),
|
||||
));
|
||||
@@ -529,10 +530,10 @@ impl InferenceEngine {
|
||||
) -> ArcIntern<String> {
|
||||
if self
|
||||
.variable_types
|
||||
.contains_key(&ArcIntern::new(name.name.clone()))
|
||||
.contains_key(name.current_interned())
|
||||
{
|
||||
let new_name = ir::gensym(&name.name);
|
||||
renames.insert(ArcIntern::new(name.name.to_string()), new_name.clone());
|
||||
let new_name = ir::gensym(&name.original_name());
|
||||
renames.insert(name.current_interned().clone(), new_name.clone());
|
||||
new_name
|
||||
} else {
|
||||
ArcIntern::new(name.to_string())
|
||||
|
||||
Reference in New Issue
Block a user