Skip to content

Commit

Permalink
Merge pull request #1085 from candy-lang/function-types-in-ast
Browse files Browse the repository at this point in the history
Function types in AST; `loop(…)`, `repeat(…)`
  • Loading branch information
JonasWanke authored Dec 14, 2024
2 parents 04a23c8 + 04933f8 commit c078657
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 9 deletions.
41 changes: 40 additions & 1 deletion compiler_v4/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,30 @@ pub struct AstTypeParameter {
// Types

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct AstType {
pub enum AstType {
Named(AstNamedType),
Function(AstFunctionType),
}

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct AstNamedType {
pub name: AstResult<AstString>,
pub type_arguments: Option<AstTypeArguments>,
}

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct AstFunctionType {
pub parameter_types: Vec<AstFunctionTypeParameterType>,
pub closing_parenthesis_error: Option<AstError>,
pub return_type: AstResult<Box<AstType>>,
pub span: Range<Offset>,
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct AstFunctionTypeParameterType {
pub type_: Box<AstType>,
pub comma_error: Option<AstError>,
}

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct AstTypeArguments {
pub span: Range<Offset>,
Expand Down Expand Up @@ -496,11 +515,31 @@ impl CollectAstErrors for AstTypeParameter {
}

impl CollectAstErrors for AstType {
fn collect_errors_to(&self, errors: &mut Vec<CompilerError>) {
match &self {
Self::Named(named) => named.collect_errors_to(errors),
Self::Function(function) => function.collect_errors_to(errors),
}
}
}
impl CollectAstErrors for AstNamedType {
fn collect_errors_to(&self, errors: &mut Vec<CompilerError>) {
self.name.collect_errors_to(errors);
self.type_arguments.collect_errors_to(errors);
}
}
impl CollectAstErrors for AstFunctionType {
fn collect_errors_to(&self, errors: &mut Vec<CompilerError>) {
self.parameter_types.collect_errors_to(errors);
self.return_type.collect_errors_to(errors);
}
}
impl CollectAstErrors for AstFunctionTypeParameterType {
fn collect_errors_to(&self, errors: &mut Vec<CompilerError>) {
self.type_.collect_errors_to(errors);
self.comma_error.collect_errors_to(errors);
}
}
impl CollectAstErrors for AstTypeArguments {
fn collect_errors_to(&self, errors: &mut Vec<CompilerError>) {
self.arguments.collect_errors_to(errors);
Expand Down
26 changes: 25 additions & 1 deletion compiler_v4/src/ast_to_hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,13 @@ impl<'a> Context<'a> {
return hir::Err;
};

let type_ = match type_ {
AstType::Named(type_) => type_,
AstType::Function(function_type) => {
self.add_error(function_type.span.clone(), "Function type is not a trait");
return hir::Err;
}
};
let Some(name) = type_.name.value() else {
return hir::Err;
};
Expand Down Expand Up @@ -873,6 +880,23 @@ impl<'a> Context<'a> {
return Type::Error;
};

let type_ = match type_ {
AstType::Named(type_) => type_,
AstType::Function(type_) => {
return Type::Function(FunctionType {
parameter_types: type_
.parameter_types
.iter()
.map(|it| self.lower_type(type_parameters, self_base_type, &*it.type_))
.collect(),
return_type: Box::new(self.lower_type(
type_parameters,
self_base_type,
type_.return_type.value().map(AsRef::as_ref),
)),
})
}
};
let Some(name) = type_.name.value() else {
return Type::Error;
};
Expand Down Expand Up @@ -1883,7 +1907,7 @@ impl<'c, 'a> BodyBuilder<'c, 'a> {
Ok(substitutions) => {
assert!(substitutions.is_empty());
self.push_lowered(
name.string.clone(),
None,
ExpressionKind::Call {
function: id,
substitutions,
Expand Down
3 changes: 2 additions & 1 deletion compiler_v4/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,14 @@ fn debug(options: DebugOptions) -> ProgramResult {
DebugOptions::Hir(options) => {
let source = fs::read_to_string(&options.path).unwrap();
let (hir, errors) = compile_hir(&options.path, &source);
println!("{}", hir.to_text(true));

if !errors.is_empty() {
for error in errors {
error!("{}", error.to_string_with_location(&source));
}
return Err(Exit::CodeContainsErrors);
}
println!("{}", hir.to_text(true));
}
DebugOptions::Mono(options) => {
let source = fs::read_to_string(&options.path).unwrap();
Expand Down
10 changes: 8 additions & 2 deletions compiler_v4/src/mono_to_c.rs
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,9 @@ impl<'h> Context<'h> {
}

self.lower_expression(declaration_name, *id, expression);
if &*expression.type_ == "Never" {
self.push("// Returns `Never`\n");
}
self.push("\n\n");
}
}
Expand Down Expand Up @@ -810,8 +813,11 @@ impl<'h> Context<'h> {
}

self.lower_body_expressions(declaration_name, &case.body);

self.push(format!("{id} = {};\n", case.body.return_value_id()));
if case.body.return_type() == "Never" {
self.push("// Returns `Never`\n");
} else {
self.push(format!("{id} = {};\n", case.body.return_value_id()));
}

self.push("break;");
}
Expand Down
78 changes: 75 additions & 3 deletions compiler_v4/src/string_to_ast/type_.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,97 @@
use super::{
literal::{closing_bracket, comma, opening_bracket},
literal::{closing_bracket, closing_parenthesis, comma, opening_bracket, opening_parenthesis},
parser::{OptionOfParser, OptionOfParserWithValue, Parser},
whitespace::{AndTrailingWhitespace, ValueAndTrailingWhitespace},
word::raw_identifier,
};
use crate::ast::{AstError, AstString, AstType, AstTypeArgument, AstTypeArguments};
use crate::ast::{
AstError, AstFunctionType, AstFunctionTypeParameterType, AstNamedType, AstString, AstType,
AstTypeArgument, AstTypeArguments,
};
use tracing::instrument;

#[instrument(level = "trace")]
pub fn type_(parser: Parser) -> Option<(Parser, AstType)> {
None.or_else(|| named_type(parser).map(|(parser, it)| (parser, AstType::Named(it))))
.or_else(|| function_type(parser).map(|(parser, it)| (parser, AstType::Function(it))))
}

#[instrument(level = "trace")]
pub fn named_type(parser: Parser) -> Option<(Parser, AstNamedType)> {
let (parser, name) = raw_identifier(parser)?.and_trailing_whitespace();
let (parser, type_arguments) = type_arguments(parser).optional(parser);
Some((
parser,
AstType {
AstNamedType {
name,
type_arguments,
},
))
}

#[instrument(level = "trace")]
pub fn function_type(parser: Parser) -> Option<(Parser, AstFunctionType)> {
let start_offset = parser.offset();
let mut parser = opening_parenthesis(parser)?.and_trailing_whitespace();

// TODO: error on duplicate type parameter names
let mut parameter_types: Vec<AstFunctionTypeParameterType> = vec![];
let mut parser_for_missing_comma_error: Option<Parser> = None;
while let Some((new_parser, parameter_type, new_parser_for_missing_comma_error)) =
function_type_parameter_type(parser.and_trailing_whitespace())
{
if let Some(parser_for_missing_comma_error) = parser_for_missing_comma_error {
parameter_types.last_mut().unwrap().comma_error = Some(
parser_for_missing_comma_error
.error_at_current_offset("This parameter type is missing a comma."),
);
}

parser = new_parser;
parameter_types.push(parameter_type);
parser_for_missing_comma_error = new_parser_for_missing_comma_error;
}
let parser = parser.and_trailing_whitespace();

let (parser, closing_parenthesis_error) = closing_parenthesis(parser)
.unwrap_or_ast_error(
parser,
"These parameter types are missing a closing parenthesis.",
)
.and_trailing_whitespace();

let (parser, return_type) =
type_(parser).unwrap_or_ast_error(parser, "This function type is missing a return type.");

Some((
parser,
AstFunctionType {
parameter_types,
closing_parenthesis_error,
return_type: return_type.map(Box::new),
span: start_offset..parser.offset(),
},
))
}
#[instrument(level = "trace")]
fn function_type_parameter_type<'a>(
parser: Parser,
) -> Option<(Parser, AstFunctionTypeParameterType, Option<Parser>)> {
let (parser, type_) = type_(parser)?.and_trailing_whitespace();

let (parser, parser_for_missing_comma_error) =
comma(parser).map_or((parser, Some(parser)), |parser| (parser, None));

Some((
parser,
AstFunctionTypeParameterType {
type_: Box::new(type_),
comma_error: None,
},
parser_for_missing_comma_error,
))
}

#[instrument(level = "trace")]
pub fn type_arguments<'s>(parser: Parser<'s>) -> Option<(Parser, AstTypeArguments)> {
let start_offset = parser.offset();
Expand Down
20 changes: 19 additions & 1 deletion packages_v5/example.candy
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,21 @@ fun needs(condition: Bool, message: Text) {
}
}

fun loop(body: () Nothing) {
body()
loop(body)
}
fun repeat(times: Int, body: () Nothing) {
needs(times.isNonNegative())
switch times.isGreaterThan(0) {
false => {},
true => {
body()
repeat(times.subtract(1), body)
},
}
}

struct List[T] = builtin
fun listFilled[T](length: Int, item: T) List[T] {
builtinListFilled(length, item)
Expand Down Expand Up @@ -538,11 +553,14 @@ fun main() Int {
print("Length: {list.length().toText()}")
print("[{list.get(0).toText()}, {list.get(1).toText()}, {list.get(2).toText()}]")

print("orld!".endsWith("World!").toText().isEmpty())

let foo = 123
let addCaptured = (x: Int) { x.add(foo) }
print("addCaptured(1) = {addCaptured(1).toText()}")

print("orld!".endsWith("World!").toText().isEmpty())
repeat(3, () { print("Hello, World!") })

0
}

Expand Down

0 comments on commit c078657

Please sign in to comment.