Implement records
This commit is contained in:
parent
8111f69640
commit
68506571a8
@ -4,6 +4,7 @@ use crate::{
|
|||||||
ast,
|
ast,
|
||||||
builtin::Builtin,
|
builtin::Builtin,
|
||||||
symbol_table::{DefId, SymbolSpec, SymbolTable},
|
symbol_table::{DefId, SymbolSpec, SymbolTable},
|
||||||
|
type_inference::TypeContext,
|
||||||
};
|
};
|
||||||
|
|
||||||
mod test;
|
mod test;
|
||||||
@ -11,19 +12,20 @@ mod types;
|
|||||||
|
|
||||||
pub use types::*;
|
pub use types::*;
|
||||||
|
|
||||||
pub fn reduce(ast: &ast::AST, symbol_table: &SymbolTable) -> ReducedIR {
|
pub fn reduce(ast: &ast::AST, symbol_table: &SymbolTable, type_context: &TypeContext) -> ReducedIR {
|
||||||
let reducer = Reducer::new(symbol_table);
|
let reducer = Reducer::new(symbol_table, type_context);
|
||||||
reducer.reduce(ast)
|
reducer.reduce(ast)
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Reducer<'a> {
|
struct Reducer<'a, 'b> {
|
||||||
symbol_table: &'a SymbolTable,
|
symbol_table: &'a SymbolTable,
|
||||||
functions: HashMap<DefId, FunctionDefinition>,
|
functions: HashMap<DefId, FunctionDefinition>,
|
||||||
|
type_context: &'b TypeContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Reducer<'a> {
|
impl<'a, 'b> Reducer<'a, 'b> {
|
||||||
fn new(symbol_table: &'a SymbolTable) -> Self {
|
fn new(symbol_table: &'a SymbolTable, type_context: &'b TypeContext) -> Self {
|
||||||
Self { symbol_table, functions: HashMap::new() }
|
Self { symbol_table, functions: HashMap::new(), type_context }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reduce(mut self, ast: &ast::AST) -> ReducedIR {
|
fn reduce(mut self, ast: &ast::AST) -> ReducedIR {
|
||||||
@ -132,20 +134,45 @@ impl<'a> Reducer<'a> {
|
|||||||
body: self.function_internal_block(body),
|
body: self.function_internal_block(body),
|
||||||
}),
|
}),
|
||||||
NamedStruct { name, fields } => {
|
NamedStruct { name, fields } => {
|
||||||
|
self.symbol_table.debug();
|
||||||
let symbol = self.symbol_table.lookup_symbol(&name.id).unwrap();
|
let symbol = self.symbol_table.lookup_symbol(&name.id).unwrap();
|
||||||
let constructor = match symbol.spec() {
|
let (tag, type_id) = match symbol.spec() {
|
||||||
SymbolSpec::RecordConstructor { tag, members: _, type_id } =>
|
SymbolSpec::RecordConstructor { tag, members: _, type_id } => (tag, type_id),
|
||||||
Expression::Callable(Callable::RecordConstructor { type_id, tag }),
|
|
||||||
e => return Expression::ReductionError(format!("Bad symbol for NamedStruct: {:?}", e)),
|
e => return Expression::ReductionError(format!("Bad symbol for NamedStruct: {:?}", e)),
|
||||||
};
|
};
|
||||||
|
|
||||||
//TODO need to order the fields correctly, which needs symbol table information
|
// Eventually, the ReducedIR should decide what field ordering is optimal.
|
||||||
// Until this happens, NamedStructs won't work
|
// For now, just do it alphabetically.
|
||||||
let mut ordered_args = vec![];
|
let mut field_order: Vec<String> = self
|
||||||
for (_name, _type_id) in fields {
|
.type_context
|
||||||
unimplemented!()
|
.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 }
|
Expression::Call { f: Box::new(constructor), args: ordered_args }
|
||||||
}
|
}
|
||||||
Index { .. } => Expression::ReductionError("Index expr not implemented".to_string()),
|
Index { .. } => Expression::ReductionError("Index expr not implemented".to_string()),
|
||||||
|
@ -11,7 +11,7 @@ fn build_ir(input: &str) -> ReducedIR {
|
|||||||
|
|
||||||
symbol_table.process_ast(&ast, &mut type_context).unwrap();
|
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.debug(&symbol_table);
|
||||||
reduced
|
reduced
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ pub enum Callable {
|
|||||||
UserDefined(DefId),
|
UserDefined(DefId),
|
||||||
Lambda { arity: u8, body: Vec<Statement> },
|
Lambda { arity: u8, body: Vec<Statement> },
|
||||||
DataConstructor { type_id: TypeId, tag: u32 },
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
|
@ -91,7 +91,7 @@ impl<'a> Schala<'a> {
|
|||||||
// TODO typechecking not working
|
// TODO typechecking not working
|
||||||
//let _overall_type = self.type_context.typecheck(&ast).map_err(SchalaError::from_type_error);
|
//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 evaluation_outputs = self.eval_state.evaluate(reduced_ir, &self.type_context, true);
|
||||||
let text_output: Result<Vec<String>, String> = evaluation_outputs.into_iter().collect();
|
let text_output: Result<Vec<String>, String> = evaluation_outputs.into_iter().collect();
|
||||||
|
@ -109,7 +109,7 @@ impl<'a, 'b> Evaluator<'a, 'b> {
|
|||||||
Expression::Callable(Callable::DataConstructor { type_id, tag }) => {
|
Expression::Callable(Callable::DataConstructor { type_id, tag }) => {
|
||||||
let arity = self.type_context.lookup_variant_arity(&type_id, tag).unwrap();
|
let arity = self.type_context.lookup_variant_arity(&type_id, tag).unwrap();
|
||||||
if arity == 0 {
|
if arity == 0 {
|
||||||
Primitive::Object { type_id, tag, items: vec![] }
|
Primitive::Object { type_id, tag, items: vec![], ordered_fields: None }
|
||||||
} else {
|
} else {
|
||||||
Primitive::Callable(Callable::DataConstructor { type_id, tag })
|
Primitive::Callable(Callable::DataConstructor { type_id, tag })
|
||||||
}
|
}
|
||||||
@ -222,14 +222,24 @@ impl<'a, 'b> Evaluator<'a, 'b> {
|
|||||||
.into());
|
.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut evaluated_args: Vec<Primitive> = vec![];
|
let mut items: Vec<Primitive> = vec![];
|
||||||
for arg in args.into_iter() {
|
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: _ } => {
|
Callable::RecordConstructor { type_id, tag, field_order } => {
|
||||||
unimplemented!()
|
//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) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,21 +121,33 @@ enum Primitive {
|
|||||||
Tuple(Vec<Primitive>),
|
Tuple(Vec<Primitive>),
|
||||||
Literal(Literal),
|
Literal(Literal),
|
||||||
Callable(Callable),
|
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 {
|
impl Primitive {
|
||||||
fn to_repl(&self, type_context: &TypeContext) -> String {
|
fn to_repl(&self, type_context: &TypeContext) -> String {
|
||||||
match self {
|
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(),
|
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!(
|
format!(
|
||||||
"{}{}",
|
"{}{}",
|
||||||
type_context.variant_local_name(type_id, *tag).unwrap(),
|
type_context.variant_local_name(type_id, *tag).unwrap(),
|
||||||
paren_wrapped(items.iter().map(|item| item.to_repl(type_context)))
|
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 {
|
Primitive::Literal(lit) => match lit {
|
||||||
Literal::Nat(n) => format!("{}", n),
|
Literal::Nat(n) => format!("{}", n),
|
||||||
Literal::Int(i) => format!("{}", i),
|
Literal::Int(i) => format!("{}", i),
|
||||||
|
@ -14,7 +14,7 @@ fn evaluate_input(input: &str) -> Result<String, String> {
|
|||||||
|
|
||||||
symbol_table.process_ast(&ast, &mut type_context).unwrap();
|
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);
|
reduced_ir.debug(&symbol_table);
|
||||||
println!("========");
|
println!("========");
|
||||||
symbol_table.debug();
|
symbol_table.debug();
|
||||||
@ -29,6 +29,10 @@ fn eval_assert(input: &str, expected: &str) {
|
|||||||
assert_eq!(evaluate_input(input), Ok(expected.to_string()));
|
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]
|
#[test]
|
||||||
fn test_basic_eval() {
|
fn test_basic_eval() {
|
||||||
eval_assert("1 + 2", "3");
|
eval_assert("1 + 2", "3");
|
||||||
@ -85,6 +89,30 @@ let b = Option::Some(10)
|
|||||||
eval_assert(source, "(Some(10), None)");
|
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]
|
#[test]
|
||||||
fn basic_if_statement() {
|
fn basic_if_statement() {
|
||||||
let source = r#"
|
let source = r#"
|
||||||
|
@ -26,7 +26,7 @@ impl TypeContext {
|
|||||||
let members = variant_builder.members;
|
let members = variant_builder.members;
|
||||||
if members.is_empty() {
|
if members.is_empty() {
|
||||||
pending_variants.push(Variant { name: variant_builder.name, members: VariantMembers::Unit });
|
pending_variants.push(Variant { name: variant_builder.name, members: VariantMembers::Unit });
|
||||||
break;
|
continue;
|
||||||
}
|
}
|
||||||
let record_variant = matches!(members.get(0).unwrap(), VariantMemberBuilder::KeyVal(..));
|
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> {
|
pub fn lookup_type(&self, type_id: &TypeId) -> Option<&DefinedType> {
|
||||||
self.defined_types.get(type_id)
|
self.defined_types.get(type_id)
|
||||||
}
|
}
|
||||||
@ -91,6 +100,7 @@ impl TypeContext {
|
|||||||
|
|
||||||
/// A type defined in program source code, as opposed to a builtin.
|
/// A type defined in program source code, as opposed to a builtin.
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct DefinedType {
|
pub struct DefinedType {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
|
||||||
@ -98,11 +108,13 @@ pub struct DefinedType {
|
|||||||
pub variants: Vec<Variant>,
|
pub variants: Vec<Variant>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Variant {
|
pub struct Variant {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub members: VariantMembers,
|
pub members: VariantMembers,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum VariantMembers {
|
pub enum VariantMembers {
|
||||||
Unit,
|
Unit,
|
||||||
// Should be non-empty
|
// Should be non-empty
|
||||||
@ -124,6 +136,7 @@ impl From<&TypeIdentifier> for PendingType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct TypeBuilder {
|
pub struct TypeBuilder {
|
||||||
name: String,
|
name: String,
|
||||||
variants: Vec<VariantBuilder>,
|
variants: Vec<VariantBuilder>,
|
||||||
@ -139,6 +152,7 @@ impl TypeBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct VariantBuilder {
|
pub struct VariantBuilder {
|
||||||
name: String,
|
name: String,
|
||||||
members: Vec<VariantMemberBuilder>,
|
members: Vec<VariantMemberBuilder>,
|
||||||
|
Loading…
Reference in New Issue
Block a user