Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: Add Else Operator #198

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
3 changes: 3 additions & 0 deletions src/cache/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,8 @@ pub struct ModuleCache<'a> {
pub error_count: usize,

pub file_cache: FileCache,

pub maybe_type: Option<TypeInfoId>,
}

pub type FileCache = HashMap<PathBuf, String>;
Expand Down Expand Up @@ -417,6 +419,7 @@ impl<'a> ModuleCache<'a> {
global_dependency_graph: DependencyGraph::default(),
diagnostics: Vec::new(),
error_count: 0,
maybe_type: Some(TypeInfoId(0)), // sentinel value
jfecher marked this conversation as resolved.
Show resolved Hide resolved
file_cache,
}
}
Expand Down
27 changes: 27 additions & 0 deletions src/cranelift_backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,33 @@ impl CodeGen for hir::If {
}
}

impl CodeGen for hir::Else {
fn codegen<'a>(&'a self, context: &mut Context<'a>, builder: &mut FunctionBuilder) -> Value {
let lhs = builder.create_block();
let then_values = context.eval_all_in_block(&self.lhs, lhs, builder);

let end = context.new_block_with_arg(&self.result_type, builder);

let else_none = builder.create_block();
builder.ins().jump(else_none, &[]);

let else_values = context.eval_all_in_block(&self.rhs, else_none, builder);
jfecher marked this conversation as resolved.
Show resolved Hide resolved

if let Some(else_values) = else_values {
builder.ins().jump(end, &else_values);
}

builder.seal_block(end);
builder.switch_to_block(end);
let end_values = builder.block_params(end);
let ret = context.array_to_value(end_values, &self.result_type);

builder.seal_block(lhs);
builder.seal_block(else_none);
ret
}
}

impl CodeGen for hir::Match {
fn codegen<'a>(&'a self, context: &mut Context<'a>, builder: &mut FunctionBuilder) -> Value {
context.codegen_match(self, builder)
Expand Down
7 changes: 7 additions & 0 deletions src/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ pub enum TypeErrorKind {
ArgumentTypeMismatch,
NonBoolInCondition,
IfBranchMismatch,
ElseBranchMismatch,
MatchPatternTypeDiffers,
MatchReturnTypeDiffers,
DoesNotMatchAnnotatedType,
Expand Down Expand Up @@ -278,6 +279,12 @@ impl Display for DiagnosticKind {
"Expected 'then' and 'else' branch types to match, but found {expected} and {actual} respectively"
)
},
DiagnosticKind::TypeError(TypeErrorKind::ElseBranchMismatch, expected, actual) => {
write!(
f,
"Expected 'expr' and 'else' branch types to match, but found {expected} and {actual} respectively"
)
},
DiagnosticKind::TypeError(TypeErrorKind::MatchPatternTypeDiffers, expected, actual) => {
write!(f, "This pattern of type {actual} does not match the type {expected} that is being matched on")
},
Expand Down
5 changes: 5 additions & 0 deletions src/hir/closures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ fn replace_env(expr: Ast, env: &Ast, definition_id: hir::DefinitionId, f: &hir::
if_expr.otherwise = Box::new(replace_env(*if_expr.otherwise, env, definition_id, f));
Ast::If(if_expr)
},
Ast::Else(mut else_expr) => {
else_expr.lhs = Box::new(replace_env(*else_expr.lhs, env, definition_id, f));
else_expr.rhs = Box::new(replace_env(*else_expr.rhs, env, definition_id, f));
Ast::Else(else_expr)
},
Ast::Sequence(mut seq) => {
seq.statements = fmap(seq.statements, |stmt| replace_env(stmt, env, definition_id, f));
Ast::Sequence(seq)
Expand Down
10 changes: 10 additions & 0 deletions src/hir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,13 @@ pub struct If {
pub result_type: Type,
}

#[derive(Debug, Clone)]
pub struct Else {
pub lhs: Box<Ast>,
pub rhs: Box<Ast>,
pub result_type: Type,
}

#[derive(Debug, Clone)]
pub struct Match {
// Unlike ast::Match this only contains the parts of the
Expand Down Expand Up @@ -262,6 +269,7 @@ pub enum Ast {
FunctionCall(FunctionCall),
Definition(Definition),
If(If),
Else(Else),
Match(Match),
Return(Return),
Sequence(Sequence),
Expand All @@ -288,6 +296,7 @@ macro_rules! dispatch_on_hir {
$crate::hir::Ast::FunctionCall(inner) => $function(inner $(, $($args),* )? ),
$crate::hir::Ast::Definition(inner) => $function(inner $(, $($args),* )? ),
$crate::hir::Ast::If(inner) => $function(inner $(, $($args),* )? ),
$crate::hir::Ast::Else(inner) => $function(inner $(, $($args),* )? ),
$crate::hir::Ast::Match(inner) => $function(inner $(, $($args),* )? ),
$crate::hir::Ast::Return(inner) => $function(inner $(, $($args),* )? ),
$crate::hir::Ast::Sequence(inner) => $function(inner $(, $($args),* )? ),
Expand Down Expand Up @@ -323,6 +332,7 @@ impl_display!(Lambda);
impl_display!(FunctionCall);
impl_display!(Definition);
impl_display!(If);
impl_display!(Else);
impl_display!(Match);
impl_display!(Return);
impl_display!(Sequence);
Expand Down
9 changes: 9 additions & 0 deletions src/hir/monomorphisation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ impl<'c> Context<'c> {
FunctionCall(call) => self.monomorphise_call(call),
Definition(definition) => self.monomorphise_definition(definition),
If(if_) => self.monomorphise_if(if_),
Else(else_) => self.monomorphise_else(else_),
Match(match_) => self.monomorphise_match(match_),
TypeDefinition(_) => unit_literal(),
TypeAnnotation(annotation) => self.monomorphise(&annotation.lhs),
Expand Down Expand Up @@ -1499,6 +1500,14 @@ impl<'c> Context<'c> {
hir::Ast::If(hir::If { condition, then, otherwise, result_type })
}

fn monomorphise_else(&mut self, else_: &ast::Else<'c>) -> hir::Ast {
let lhs = Box::new(self.monomorphise(&else_.lhs));
let rhs = Box::new(self.monomorphise(&else_.rhs));
let result_type = self.convert_type(else_.typ.as_ref().unwrap());

hir::Ast::Else(hir::Else { lhs, rhs, result_type })
}

fn monomorphise_return(&mut self, return_: &ast::Return<'c>) -> hir::Ast {
hir::Ast::Return(hir::Return { expression: Box::new(self.monomorphise(&return_.expression)) })
}
Expand Down
10 changes: 10 additions & 0 deletions src/hir/printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,16 @@ impl FmtAst for If {
}
}

impl FmtAst for Else {
fn fmt_ast(&self, printer: &mut AstPrinter, f: &mut Formatter) -> fmt::Result {
write!(f, "(")?;
printer.block(self.lhs.as_ref(), f)?;
write!(f, " else ")?;
printer.block(self.rhs.as_ref(), f)?;
write!(f, ") ")
jfecher marked this conversation as resolved.
Show resolved Hide resolved
}
}

impl FmtAst for Return {
fn fmt_ast(&self, printer: &mut AstPrinter, f: &mut Formatter) -> fmt::Result {
write!(f, "return ")?;
Expand Down
39 changes: 39 additions & 0 deletions src/llvm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,45 @@ impl<'g> CodeGen<'g> for hir::If {
}
}

impl<'g> CodeGen<'g> for hir::Else {
fn codegen(&self, generator: &mut Generator<'g>) -> BasicValueEnum<'g> {
let current_function = generator.current_function();
let end_block = generator.context.append_basic_block(current_function, "end_else");

// Setup conditional jump
let else_block = generator.context.append_basic_block(current_function, "else");

let (if_type, then_option) = generator.codegen_branch(&self.lhs, end_block);

generator.builder.position_at_end(else_block);
let (_, else_option) = generator.codegen_branch(&self.rhs, end_block);

// Create phi at the end of the if beforehand
generator.builder.position_at_end(end_block);

// Some of the branches may have terminated early. We need to check each case to
// determine which we should add to the phi or if we should even create a phi at all.
match (then_option, else_option) {
(Some((then_value, then_branch)), Some((else_value, else_branch))) => {
let phi = generator.builder.build_phi(then_value.get_type(), "if_result").expect("Could not build phi");

phi.add_incoming(&[(&then_value, then_branch), (&else_value, else_branch)]);
phi.as_basic_value()
},
(Some((then_value, _)), None) => then_value,
(None, Some((else_value, _))) => else_value,
(None, None) => {
generator.builder.build_unreachable().expect("Could not create 'unreachable' for if");

// Block is unreachable but we still need to return an undef value.
// If we return None the compiler would crash while compiling
// `2 + if true return "uh" else return "oh"`
Generator::undef_value(if_type)
},
}
}
}

impl<'g> CodeGen<'g> for hir::Match {
fn codegen(&self, generator: &mut Generator<'g>) -> BasicValueEnum<'g> {
generator.codegen_tree(self)
Expand Down
2 changes: 2 additions & 0 deletions src/nameresolution/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ pub fn import_prelude(resolver: &mut NameResolver, cache: &mut ModuleCache<'_>)
if let Some(id) = declare_module(&prelude_dir, cache, Location::builtin()) {
let exports = define_module(id, cache, Location::builtin()).unwrap();
resolver.current_scope().import(exports, cache, Location::builtin(), &HashSet::new());
let maybe_id = resolver.current_scope().types.get("Maybe").unwrap().clone();
cache.maybe_type = Some(maybe_id);
}
}

Expand Down
12 changes: 12 additions & 0 deletions src/nameresolution/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1065,6 +1065,18 @@ impl<'c> Resolvable<'c> for ast::If<'c> {
}
}

impl<'c> Resolvable<'c> for ast::Else<'c> {
fn declare(&mut self, _resolver: &mut NameResolver, _cache: &mut ModuleCache<'c>) {}

fn define(&mut self, resolver: &mut NameResolver, cache: &mut ModuleCache<'c>) {
self.lhs.define(resolver, cache);

resolver.push_scope(cache);
self.rhs.define(resolver, cache);
resolver.pop_scope(cache, true, None);
}
}

impl<'c> Resolvable<'c> for ast::Match<'c> {
fn declare(&mut self, _resolver: &mut NameResolver, _cache: &mut ModuleCache<'c>) {}

Expand Down
15 changes: 15 additions & 0 deletions src/parser/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,14 @@ pub struct If<'a> {
pub location: Location<'a>,
pub typ: Option<types::Type>,
}
// Maybe a then a else expression
#[derive(Debug, Clone)]
pub struct Else<'a> {
pub lhs: Box<Ast<'a>>,
pub rhs: Box<Ast<'a>>,
pub location: Location<'a>,
pub typ: Option<types::Type>,
}

/// match expression
/// | pattern1 -> branch1
Expand Down Expand Up @@ -413,6 +421,7 @@ pub enum Ast<'a> {
FunctionCall(FunctionCall<'a>),
Definition(Definition<'a>),
If(If<'a>),
Else(Else<'a>),
Match(Match<'a>),
TypeDefinition(TypeDefinition<'a>),
TypeAnnotation(TypeAnnotation<'a>),
Expand Down Expand Up @@ -588,6 +597,10 @@ impl<'a> Ast<'a> {
}
}

pub fn else_expr(lhs: Ast<'a>, rhs: Ast<'a>, location: Location<'a>) -> Ast<'a> {
Ast::Else(Else { lhs: Box::new(lhs), rhs: Box::new(rhs), location, typ: None })
}

pub fn definition(pattern: Ast<'a>, expr: Ast<'a>, location: Location<'a>) -> Ast<'a> {
Ast::Definition(Definition {
pattern: Box::new(pattern),
Expand Down Expand Up @@ -736,6 +749,7 @@ macro_rules! dispatch_on_expr {
$crate::parser::ast::Ast::FunctionCall(inner) => $function(inner $(, $($args),* )? ),
$crate::parser::ast::Ast::Definition(inner) => $function(inner $(, $($args),* )? ),
$crate::parser::ast::Ast::If(inner) => $function(inner $(, $($args),* )? ),
$crate::parser::ast::Ast::Else(inner) => $function(inner $(, $($args),* )? ),
$crate::parser::ast::Ast::Match(inner) => $function(inner $(, $($args),* )? ),
$crate::parser::ast::Ast::TypeDefinition(inner) => $function(inner $(, $($args),* )? ),
$crate::parser::ast::Ast::TypeAnnotation(inner) => $function(inner $(, $($args),* )? ),
Expand Down Expand Up @@ -776,6 +790,7 @@ impl_locatable_for!(Lambda);
impl_locatable_for!(FunctionCall);
impl_locatable_for!(Definition);
impl_locatable_for!(If);
impl_locatable_for!(Else);
impl_locatable_for!(Match);
impl_locatable_for!(TypeDefinition);
impl_locatable_for!(TypeAnnotation);
Expand Down
14 changes: 12 additions & 2 deletions src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ fn expression<'a, 'b>(input: Input<'a, 'b>) -> AstResult<'a, 'b> {
fn term<'a, 'b>(input: Input<'a, 'b>) -> AstResult<'a, 'b> {
match input[0].0 {
Token::If => if_expr(input),
Token::Else => else_expr(input),
Token::Loop => loop_expr(input),
Token::Match => match_expr(input),
Token::Handle => handle_expr(input),
Expand Down Expand Up @@ -505,7 +506,7 @@ parser!(if_expr loc =
_ !<- maybe_newline;
_ !<- expect(Token::Then);
then !<- block_or_statement;
otherwise !<- maybe(else_expr);
otherwise !<- maybe(without_else_expr);
Ast::if_expr(condition, then, otherwise, loc)
);

Expand Down Expand Up @@ -632,13 +633,22 @@ parser!(match_branch _loc -> 'b (Ast<'b>, Ast<'b>) =
(pattern, branch)
);

parser!(else_expr _loc =
parser!(without_else_expr _loc =
_ <- maybe_newline;
expr <- block_or_statement;
_ <- expect(Token::Else);
otherwise !<- block_or_statement;
otherwise
);

parser!(else_expr _loc =
_ <- maybe_newline;
expr <- block_or_statement;
_ <- expect(Token::Else);
otherwise !<- block_or_statement;
Ast::else_expr(expr, otherwise, _loc)
jfecher marked this conversation as resolved.
Show resolved Hide resolved
);

/// A function_argument is a unary expr or a member_access of
/// 1-n arguments.
fn function_argument<'a, 'b>(input: Input<'a, 'b>) -> AstResult<'a, 'b> {
Expand Down
6 changes: 6 additions & 0 deletions src/parser/pretty_printer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ impl<'a> Display for ast::If<'a> {
}
}

impl<'a> Display for ast::Else<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "({} else {})", self.lhs, self.rhs)
}
}

impl<'a> Display for ast::Match<'a> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "(match {}", self.expression)?;
Expand Down
22 changes: 22 additions & 0 deletions src/types/typechecker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1728,6 +1728,28 @@ impl<'a> Inferable<'a> for ast::If<'a> {
}
}

impl<'a> Inferable<'a> for ast::Else<'a> {
fn infer_impl(&mut self, cache: &mut ModuleCache<'a>) -> TypeResult {
// Create new serogate Variable x
jfecher marked this conversation as resolved.
Show resolved Hide resolved
let x = next_type_variable(cache);
//let x = Type::TypeVariable(id);
// Construct a Maybe x Type
//let maybe_typeinfo_id = cache.type_infos.get(cache.maybe_type.0);
anoojpatel marked this conversation as resolved.
Show resolved Hide resolved
let maybe_type = UserDefined(cache.maybe_type.expect("Maybe type should be befined from importing prelude"));
let maybe_x = TypeApplication(Box::new(maybe_type), vec![x.clone()]);
// Unify Maybe x with expr and raise if Type is mismatched
let mut lhs = infer(self.lhs.as_mut(), cache);
unify(&maybe_x, &mut lhs.typ, self.lhs.locate(), cache, TE::ElseBranchMismatch);

// Unify x with a and raise if Type is mismatched
let mut rhs = infer(self.rhs.as_mut(), cache);
lhs.combine(&mut rhs, cache);

unify(&x, &rhs.typ, self.location, cache, TE::ArgumentTypeMismatch);
lhs.with_type(rhs.typ)
}
}

impl<'a> Inferable<'a> for ast::Match<'a> {
fn infer_impl(&mut self, cache: &mut ModuleCache<'a>) -> TypeResult {
let error_count = cache.error_count();
Expand Down
1 change: 1 addition & 0 deletions src/types/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ impl_typed_for!(Lambda);
impl_typed_for!(FunctionCall);
impl_typed_for!(Definition);
impl_typed_for!(If);
impl_typed_for!(Else);
impl_typed_for!(Match);
impl_typed_for!(TypeDefinition);
impl_typed_for!(TypeAnnotation);
Expand Down
Loading