Implement records

This commit is contained in:
Greg Shuflin 2021-10-29 22:03:34 -07:00
parent 8111f69640
commit 68506571a8
8 changed files with 119 additions and 28 deletions

View File

@ -4,6 +4,7 @@ use crate::{
ast,
builtin::Builtin,
symbol_table::{DefId, SymbolSpec, SymbolTable},
type_inference::TypeContext,
};
mod test;
@ -11,19 +12,20 @@ mod types;
pub use types::*;
pub fn reduce(ast: &ast::AST, symbol_table: &SymbolTable) -> ReducedIR {
let reducer = Reducer::new(symbol_table);
pub fn reduce(ast: &ast::AST, symbol_table: &SymbolTable, type_context: &TypeContext) -> ReducedIR {
let reducer = Reducer::new(symbol_table, type_context);
reducer.reduce(ast)
}
struct Reducer<'a> {
struct Reducer<'a, 'b> {
symbol_table: &'a SymbolTable,
functions: HashMap<DefId, FunctionDefinition>,
type_context: &'b TypeContext,
}
impl<'a> Reducer<'a> {
fn new(symbol_table: &'a SymbolTable) -> Self {
Self { symbol_table, functions: HashMap::new() }
impl<'a, 'b> Reducer<'a, 'b> {
fn new(symbol_table: &'a SymbolTable, type_context: &'b TypeContext) -> Self {
Self { symbol_table, functions: HashMap::new(), type_context }
}
fn reduce(mut self, ast: &ast::AST) -> ReducedIR {
@ -132,20 +134,45 @@ impl<'a> Reducer<'a> {
body: self.function_internal_block(body),
}),
NamedStruct { name, fields } => {
self.symbol_table.debug();
let symbol = self.symbol_table.lookup_symbol(&name.id).unwrap();
let constructor = match symbol.spec() {
SymbolSpec::RecordConstructor { tag, members: _, type_id } =>
Expression::Callable(Callable::RecordConstructor { type_id, tag }),
let (tag, type_id) = match symbol.spec() {
SymbolSpec::RecordConstructor { tag, members: _, type_id } => (tag, type_id),
e => return Expression::ReductionError(format!("Bad symbol for NamedStruct: {:?}", e)),
};
//TODO need to order the fields correctly, which needs symbol table information
// Until this happens, NamedStructs won't work
let mut ordered_args = vec![];
for (_name, _type_id) in fields {
unimplemented!()
// Eventually, the ReducedIR should decide what field ordering is optimal.
// For now, just do it alphabetically.
let mut field_order: Vec<String> = self
.type_context
.lookup_record_members(&type_id, tag)
.unwrap()
.iter()
.map(|(field, _type_id)| field)
.cloned()
.collect();
field_order.sort_unstable();
let mut field_map = HashMap::new();
for (name, expr) in fields.iter() {
field_map.insert(name.as_ref(), expr);
}
let mut ordered_args = vec![];
for field in field_order.iter() {
let expr = match field_map.get(&field) {
Some(expr) => expr,
None =>
return Expression::ReductionError(format!(
"Field {} not specified for record {}",
field, name
)),
};
ordered_args.push(self.expression(expr));
}
let constructor =
Expression::Callable(Callable::RecordConstructor { type_id, tag, field_order });
Expression::Call { f: Box::new(constructor), args: ordered_args }
}
Index { .. } => Expression::ReductionError("Index expr not implemented".to_string()),

View File

@ -11,7 +11,7 @@ fn build_ir(input: &str) -> ReducedIR {
symbol_table.process_ast(&ast, &mut type_context).unwrap();
let reduced = reduce(&ast, &symbol_table);
let reduced = reduce(&ast, &symbol_table, &type_context);
reduced.debug(&symbol_table);
reduced
}

View File

@ -74,7 +74,7 @@ pub enum Callable {
UserDefined(DefId),
Lambda { arity: u8, body: Vec<Statement> },
DataConstructor { type_id: TypeId, tag: u32 },
RecordConstructor { type_id: TypeId, tag: u32 },
RecordConstructor { type_id: TypeId, tag: u32, field_order: Vec<String> },
}
#[derive(Debug, Clone)]

View File

@ -91,7 +91,7 @@ impl<'a> Schala<'a> {
// TODO typechecking not working
//let _overall_type = self.type_context.typecheck(&ast).map_err(SchalaError::from_type_error);
let reduced_ir = reduced_ir::reduce(&ast, &self.symbol_table);
let reduced_ir = reduced_ir::reduce(&ast, &self.symbol_table, &self.type_context);
let evaluation_outputs = self.eval_state.evaluate(reduced_ir, &self.type_context, true);
let text_output: Result<Vec<String>, String> = evaluation_outputs.into_iter().collect();

View File

@ -109,7 +109,7 @@ impl<'a, 'b> Evaluator<'a, 'b> {
Expression::Callable(Callable::DataConstructor { type_id, tag }) => {
let arity = self.type_context.lookup_variant_arity(&type_id, tag).unwrap();
if arity == 0 {
Primitive::Object { type_id, tag, items: vec![] }
Primitive::Object { type_id, tag, items: vec![], ordered_fields: None }
} else {
Primitive::Callable(Callable::DataConstructor { type_id, tag })
}
@ -222,14 +222,24 @@ impl<'a, 'b> Evaluator<'a, 'b> {
.into());
}
let mut evaluated_args: Vec<Primitive> = vec![];
let mut items: Vec<Primitive> = vec![];
for arg in args.into_iter() {
evaluated_args.push(self.expression(arg)?);
items.push(self.expression(arg)?);
}
Ok(Primitive::Object { type_id, tag, items: evaluated_args })
Ok(Primitive::Object { type_id, tag, items, ordered_fields: None })
}
Callable::RecordConstructor { type_id: _, tag: _ } => {
unimplemented!()
Callable::RecordConstructor { type_id, tag, field_order } => {
//TODO maybe I'll want to do a runtime check of the evaluated fields
/*
let record_members = self.type_context.lookup_record_members(type_id, tag)
.ok_or(format!("Runtime record lookup for: {} {} not found", type_id, tag).into())?;
*/
let mut items: Vec<Primitive> = vec![];
for arg in args.into_iter() {
items.push(self.expression(arg)?);
}
Ok(Primitive::Object { type_id, tag, items, ordered_fields: Some(field_order) })
}
}
}

View File

@ -121,21 +121,33 @@ enum Primitive {
Tuple(Vec<Primitive>),
Literal(Literal),
Callable(Callable),
Object { type_id: TypeId, tag: u32, items: Vec<Primitive> },
Object { type_id: TypeId, tag: u32, ordered_fields: Option<Vec<String>>, items: Vec<Primitive> },
}
impl Primitive {
fn to_repl(&self, type_context: &TypeContext) -> String {
match self {
Primitive::Object { type_id, items, tag } if items.is_empty() =>
Primitive::Object { type_id, items, tag, ordered_fields: _ } if items.is_empty() =>
type_context.variant_local_name(type_id, *tag).unwrap().to_string(),
Primitive::Object { type_id, items, tag } => {
Primitive::Object { type_id, items, tag, ordered_fields: None } => {
format!(
"{}{}",
type_context.variant_local_name(type_id, *tag).unwrap(),
paren_wrapped(items.iter().map(|item| item.to_repl(type_context)))
)
}
Primitive::Object { type_id, items, tag, ordered_fields: Some(fields) } => {
let mut buf = format!("{}", type_context.variant_local_name(type_id, *tag).unwrap());
write!(buf, " {{ ").unwrap();
for item in fields.iter().zip(items.iter()).map(Some).intersperse(None) {
match item {
Some((name, val)) => write!(buf, "{}: {}", name, val.to_repl(type_context)).unwrap(),
None => write!(buf, ", ").unwrap(),
}
}
write!(buf, " }}").unwrap();
buf
}
Primitive::Literal(lit) => match lit {
Literal::Nat(n) => format!("{}", n),
Literal::Int(i) => format!("{}", i),

View File

@ -14,7 +14,7 @@ fn evaluate_input(input: &str) -> Result<String, String> {
symbol_table.process_ast(&ast, &mut type_context).unwrap();
let reduced_ir = crate::reduced_ir::reduce(&ast, &symbol_table);
let reduced_ir = crate::reduced_ir::reduce(&ast, &symbol_table, &type_context);
reduced_ir.debug(&symbol_table);
println!("========");
symbol_table.debug();
@ -29,6 +29,10 @@ fn eval_assert(input: &str, expected: &str) {
assert_eq!(evaluate_input(input), Ok(expected.to_string()));
}
fn eval_assert_failure(input: &str, expected: &str) {
assert_eq!(evaluate_input(input), Err(expected.to_string()));
}
#[test]
fn test_basic_eval() {
eval_assert("1 + 2", "3");
@ -85,6 +89,30 @@ let b = Option::Some(10)
eval_assert(source, "(Some(10), None)");
}
#[test]
fn adt_output_2() {
let source = r#"
type Gobble = Unknown | Rufus { a: Int, torrid: Nat }
let b = Gobble::Rufus { a: 3, torrid: 99 }
b
"#;
eval_assert(source, "Rufus { a: 3, torrid: 99 }");
let source = r#"
type Gobble = Unknown | Rufus { a: Int, torrid: Nat }
let b = Gobble::Rufus { torrid: 3, a: 84 }
b
"#;
eval_assert(source, "Rufus { a: 84, torrid: 3 }");
let source = r#"
type Gobble = Unknown | Rufus { a: Int, torrid: Nat }
let b = Gobble::Rufus { a: 84 }
b
"#;
eval_assert_failure(source, "Field torrid not specified for record Gobble::Rufus");
}
#[test]
fn basic_if_statement() {
let source = r#"

View File

@ -26,7 +26,7 @@ impl TypeContext {
let members = variant_builder.members;
if members.is_empty() {
pending_variants.push(Variant { name: variant_builder.name, members: VariantMembers::Unit });
break;
continue;
}
let record_variant = matches!(members.get(0).unwrap(), VariantMemberBuilder::KeyVal(..));
@ -84,6 +84,15 @@ impl TypeContext {
)
}
pub fn lookup_record_members(&self, type_id: &TypeId, tag: u32) -> Option<&[(String, TypeId)]> {
self.defined_types.get(type_id).and_then(|defined| defined.variants.get(tag as usize)).and_then(
|variant| match &variant.members {
VariantMembers::Record(items) => Some(items.as_ref()),
_ => None,
},
)
}
pub fn lookup_type(&self, type_id: &TypeId) -> Option<&DefinedType> {
self.defined_types.get(type_id)
}
@ -91,6 +100,7 @@ impl TypeContext {
/// A type defined in program source code, as opposed to a builtin.
#[allow(dead_code)]
#[derive(Debug)]
pub struct DefinedType {
pub name: String,
@ -98,11 +108,13 @@ pub struct DefinedType {
pub variants: Vec<Variant>,
}
#[derive(Debug)]
pub struct Variant {
pub name: String,
pub members: VariantMembers,
}
#[derive(Debug)]
pub enum VariantMembers {
Unit,
// Should be non-empty
@ -124,6 +136,7 @@ impl From<&TypeIdentifier> for PendingType {
}
}
#[derive(Debug)]
pub struct TypeBuilder {
name: String,
variants: Vec<VariantBuilder>,
@ -139,6 +152,7 @@ impl TypeBuilder {
}
}
#[derive(Debug)]
pub struct VariantBuilder {
name: String,
members: Vec<VariantMemberBuilder>,