Skip to content

Commit

Permalink
Merge pull request #23 from eckertliam/18-implement-type-aliases
Browse files Browse the repository at this point in the history
closes #18 implement type aliases
  • Loading branch information
eckertliam authored Jan 17, 2025
2 parents 998e765 + 3530376 commit 7478838
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 22 deletions.
9 changes: 9 additions & 0 deletions src/frontend/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ pub enum Statement {
generics: Vec<Type>,// TODO: add generics
fields: Vec<(String, Type)>,
},
AliasDecl {
name: String,
generics: Vec<Type>,
ty: Type,
},
}

impl Statement {
Expand Down Expand Up @@ -196,6 +201,10 @@ impl Statement {
pub fn new_record_decl(name: String, generics: Vec<Type>, fields: Vec<(String, Type)>, line: usize) -> Located<Self> {
Located::new(Self::RecordDecl { name, generics, fields }, line)
}

pub fn new_alias_decl(name: String, generics: Vec<Type>, ty: Type, line: usize) -> Located<Self> {
Located::new(Self::AliasDecl { name, generics, ty }, line)
}
}

pub type Block = Vec<Located<Statement>>;
37 changes: 36 additions & 1 deletion src/frontend/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ fn statement<'a>(parser: &mut Parser<'a>) -> Option<Located<Statement>> {
TokenKind::LeftBrace => block_statement(parser),
TokenKind::Return => return_statement(parser),
TokenKind::Record => record_declaration(parser),
TokenKind::Type => alias_declaration(parser),
_ => expression_statement(parser),
}
}
Expand Down Expand Up @@ -644,7 +645,24 @@ fn record_declaration<'a>(parser: &mut Parser<'a>) -> Option<Located<Statement>>
let name = parser.previous.lexeme.to_string();
let fields = parse_record_fields(parser)?;
// TODO: add generic handling
Some(Statement::new_record_decl(name, vec![], fields, line))
let generics = vec![];
Some(Statement::new_record_decl(name, generics, fields, line))
}

fn alias_declaration<'a>(parser: &mut Parser<'a>) -> Option<Located<Statement>> {
// advance over the alias keyword
advance(parser);
// advance over the identifier
advance(parser);
let line = parser.previous.line;
let name = parser.previous.lexeme.to_string();
// TODO: handle generics
let generics = vec![];
// expect an equals sign
consume(parser, TokenKind::Eq, "Expected an equals sign after alias declaration.")?;
// parse the type
let ty = type_expr(parser)?;
Some(Statement::new_alias_decl(name, generics, ty, line))
}

// top level parsing =====
Expand Down Expand Up @@ -1003,5 +1021,22 @@ mod tests {
],
});
}

#[test]
fn test_alias_declaration() {
let scanner = Scanner::new("type Vec = [f32; 10]");
let mut parser = Parser::new(scanner);
let stmt = statement(&mut parser);
assert!(stmt.is_some());
let alias_decl = stmt.unwrap().node;
assert_eq!(alias_decl, Statement::AliasDecl {
name: "Vec".to_string(),
generics: vec![],
ty: Type::Array {
element_type: Box::new(Type::F32),
size: 10,
},
});
}
}

3 changes: 3 additions & 0 deletions src/frontend/scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ pub enum TokenKind {
Break,
Continue,
Record,
Type,
// Literals
Number,
String,
Expand Down Expand Up @@ -103,6 +104,7 @@ impl Display for TokenKind {
TokenKind::Break => write!(f, "break"),
TokenKind::Continue => write!(f, "continue"),
TokenKind::Record => write!(f, "record"),
TokenKind::Type => write!(f, "type"),
TokenKind::Number => write!(f, "<number>"),
TokenKind::String => write!(f, "<string>"),
TokenKind::Char => write!(f, "<char>"),
Expand Down Expand Up @@ -215,6 +217,7 @@ impl<'a> Scanner<'a> {
"break" => TokenKind::Break,
"continue" => TokenKind::Continue,
"record" => TokenKind::Record,
"type" => TokenKind::Type,
_ => TokenKind::Identifier,
};

Expand Down
61 changes: 42 additions & 19 deletions src/frontend/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,6 @@ pub enum Type {
}
}

#[derive(Debug, PartialEq, Clone)]
pub struct Record {
pub name: String,
// type vars
pub generics: Vec<Type>,
pub fields: Vec<(String, Type)>,
}

impl Display for Type {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Expand Down Expand Up @@ -83,14 +75,45 @@ impl Display for Type {
}
}

#[derive(Debug, PartialEq, Clone)]
pub enum Udt {
Record {
name: String,
generics: Vec<Type>,
fields: Vec<(String, Type)>,
},
Alias {
name: String,
generics: Vec<Type>,
ty: Type,
}
}

impl Udt {
pub fn new_record(name: String, generics: Vec<Type>, fields: Vec<(String, Type)>) -> Self {
Self::Record { name, generics, fields }
}

pub fn new_alias(name: String, generics: Vec<Type>, ty: Type) -> Self {
Self::Alias { name, generics, ty }
}

pub fn name(&self) -> &str {
match self {
Udt::Record { name, .. } => name,
Udt::Alias { name, .. } => name,
}
}
}

type FnSig = (Vec<Type>, Type);

#[derive(Clone)]
pub struct TypeEnv {
pub fn_sig: Option<FnSig>,
parent: Option<Rc<RefCell<TypeEnv>>>,
bindings: HashMap<String, Type>,
record_types: HashMap<String, Record>,
udts: HashMap<String, Udt>,
}

impl TypeEnv {
Expand All @@ -100,7 +123,7 @@ impl TypeEnv {
fn_sig: None,
parent: None,
bindings: HashMap::new(),
record_types: HashMap::new(),
udts: HashMap::new(),
}
}

Expand All @@ -110,7 +133,7 @@ impl TypeEnv {
fn_sig: None,
parent: Some(Rc::new(RefCell::new(self.clone()))),
bindings: HashMap::new(),
record_types: HashMap::new(),
udts: HashMap::new(),
}
}

Expand All @@ -120,7 +143,7 @@ impl TypeEnv {
fn_sig: Some((params, return_type)),
parent: Some(Rc::new(RefCell::new(self.clone()))),
bindings: HashMap::new(),
record_types: HashMap::new(),
udts: HashMap::new(),
}
}

Expand All @@ -131,8 +154,8 @@ impl TypeEnv {
.or_else(|| self.parent.as_ref().and_then(|p| p.borrow().get(ident)))
}

pub fn get_record(&self, ident: &str) -> Option<Record> {
self.record_types.get(ident).cloned()
pub fn get_udt(&self, ident: &str) -> Option<Udt> {
self.udts.get(ident).cloned()
}

/// inserts a type into the top level type environment
Expand All @@ -144,16 +167,16 @@ impl TypeEnv {
}
}

pub fn insert_record_top(&mut self, record: Record) -> Result<(), String> {
pub fn insert_udt_top(&mut self, udt: Udt) -> Result<(), String> {
if self.parent.is_none() {
// ensure that the record name is not already in the type env
if self.record_types.contains_key(&record.name) {
return Err(format!("Record {} already exists in the type environment", record.name));
if self.udts.contains_key(udt.name()) {
return Err(format!("UDT {} already exists in the type environment", udt.name()));
}
self.record_types.insert(record.name.clone(), record);
self.udts.insert(udt.name().to_string(), udt);
Ok(())
} else {
self.parent.as_ref().unwrap().borrow_mut().insert_record_top(record)
self.parent.as_ref().unwrap().borrow_mut().insert_udt_top(udt)
}
}

Expand Down
23 changes: 21 additions & 2 deletions src/passes/type_pass.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{Block, Expression, Program, Record, Statement, TokenKind, Type, TypeEnv};
use crate::{Block, Expression, Program, Statement, TokenKind, Type, TypeEnv, Udt};

// TODO: add handling for type var identifiers in expressions such as let x: TypeVar = 1; then x is a type var and let y = x + 1; will need resolution
// TODO: add handling for function calls in which the the callee has type vars in its signature
Expand All @@ -13,7 +13,10 @@ fn add_udt_pass(program: &Program, type_env: &mut TypeEnv) -> Result<Program, St
for stmt in program.statements.iter() {
match &stmt.node {
Statement::RecordDecl { name, fields, generics } => {
type_env.insert_record_top(Record { name: name.clone(), generics: generics.clone(), fields: fields.clone() })?;
type_env.insert_udt_top(Udt::Record { name: name.clone(), generics: generics.clone(), fields: fields.clone() })?;
}
Statement::AliasDecl { name, generics, ty } => {
type_env.insert_udt_top(Udt::Alias { name: name.clone(), generics: generics.clone(), ty: ty.clone() })?;
}
_ => new_program.add_statement(stmt.clone()),
}
Expand Down Expand Up @@ -406,6 +409,22 @@ mod tests {
env
}

#[test]
fn test_alias_declaration() {
let mut program = Program::new();
program.add_statement(Statement::new_alias_decl("Vec".to_string(), vec![], Type::Array { element_type: Box::new(Type::I64), size: 10 }, 1));
let new_env = type_check_program(&program).unwrap();
assert_eq!(new_env.get_udt("Vec"), Some(Udt::Alias { name: "Vec".to_string(), generics: vec![], ty: Type::Array { element_type: Box::new(Type::I64), size: 10 } }));
}

#[test]
fn test_record_declaration() {
let mut program = Program::new();
program.add_statement(Statement::new_record_decl("Point".to_string(), vec![], vec![("x".to_string(), Type::I64), ("y".to_string(), Type::I64)], 1));
let new_env = type_check_program(&program).unwrap();
assert_eq!(new_env.get_udt("Point"), Some(Udt::Record { name: "Point".to_string(), generics: vec![], fields: vec![("x".to_string(), Type::I64), ("y".to_string(), Type::I64)] }));
}

#[test]
fn test_literal_types() {
let mut env = create_test_env();
Expand Down

0 comments on commit 7478838

Please sign in to comment.