feat: add foreign function support
This commit is contained in:
@@ -57,6 +57,12 @@ pub enum DeclKind<P: Phase = Untyped> {
|
||||
return_type: P::ReturnType,
|
||||
body: Stmt<P>,
|
||||
},
|
||||
ForeignFunction {
|
||||
name: String,
|
||||
name_span: Span,
|
||||
params: Vec<P::ParamType>,
|
||||
return_type: P::ReturnType,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
@@ -161,6 +167,10 @@ pub enum ExprKind<P: Phase = Untyped> {
|
||||
expr: Box<Expr<P>>,
|
||||
ty: P::CastType,
|
||||
},
|
||||
Call {
|
||||
callee: Box<Expr<P>>,
|
||||
args: Vec<Expr<P>>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
|
||||
@@ -64,6 +64,7 @@ impl<'src> Lexer<'src> {
|
||||
|
||||
match &self.source[start..self.cursor] {
|
||||
"fn" => TokenKind::Fn,
|
||||
"foreign" => TokenKind::Foreign,
|
||||
"if" => TokenKind::If,
|
||||
"as" => TokenKind::As,
|
||||
"else" => TokenKind::Else,
|
||||
@@ -249,7 +250,7 @@ mod test {
|
||||
#[test]
|
||||
fn identifiers() {
|
||||
assert_eq!(
|
||||
tokenize("HELLO _hello _0@ fn if else return let while break continue as"),
|
||||
tokenize("HELLO _hello _0@ fn if else return let while break continue as foreign"),
|
||||
vec![
|
||||
Token::new(TokenKind::Identifier, "HELLO", Span::new(0, 5)),
|
||||
Token::new(TokenKind::Identifier, "_hello", Span::new(6, 12)),
|
||||
@@ -264,6 +265,7 @@ mod test {
|
||||
Token::new(TokenKind::Break, "break", Span::new(45, 50)),
|
||||
Token::new(TokenKind::Continue, "continue", Span::new(51, 59)),
|
||||
Token::new(TokenKind::As, "as", Span::new(60, 62)),
|
||||
Token::new(TokenKind::Foreign, "foreign", Span::new(63, 70)),
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -135,6 +135,7 @@ impl<'src> Parser<'src> {
|
||||
|
||||
match peek_token.kind {
|
||||
TokenKind::Fn => self.parse_function_decl(),
|
||||
TokenKind::Foreign => self.parse_foreign_function_decl(),
|
||||
|
||||
_ => Err(ParseError::new(
|
||||
format!(
|
||||
@@ -185,6 +186,45 @@ impl<'src> Parser<'src> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Parses a foreign function declaration.
|
||||
///
|
||||
/// ```ebnf
|
||||
/// foreign_function_decl = "foreign" "fn" IDENTIFIER "(" function_params ")" [ "->" type ] ";" ;
|
||||
/// ```
|
||||
fn parse_foreign_function_decl(&mut self) -> ParseResult<Decl> {
|
||||
let foreign_token = self.expect(TokenKind::Foreign)?;
|
||||
self.expect(TokenKind::Fn)?;
|
||||
|
||||
let (name, name_span) = {
|
||||
let ident_token = self.expect(TokenKind::Identifier)?;
|
||||
(ident_token.text.to_string(), ident_token.span)
|
||||
};
|
||||
|
||||
self.expect(TokenKind::LParen)?;
|
||||
let params = self.parse_function_params()?;
|
||||
self.expect(TokenKind::RParen)?;
|
||||
|
||||
let return_type = if self.is_peek(TokenKind::Arrow) {
|
||||
self.advance();
|
||||
Some(self.parse_type()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let semi_token = self.expect(TokenKind::Semicolon)?;
|
||||
let span = foreign_token.span.join(semi_token.span);
|
||||
|
||||
Ok(Decl {
|
||||
kind: DeclKind::ForeignFunction {
|
||||
name,
|
||||
name_span,
|
||||
params,
|
||||
return_type,
|
||||
},
|
||||
span,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parses the function parameter list.
|
||||
///
|
||||
/// ```ebnf
|
||||
@@ -581,6 +621,38 @@ impl<'src> Parser<'src> {
|
||||
continue;
|
||||
}
|
||||
|
||||
if peek_token.kind == TokenKind::LParen {
|
||||
let left_bp = 30; // Function calls have very high precedence
|
||||
if left_bp < min_bp {
|
||||
break;
|
||||
}
|
||||
self.advance(); // consume `(`
|
||||
|
||||
let mut args = Vec::new();
|
||||
if !self.is_peek(TokenKind::RParen) {
|
||||
loop {
|
||||
args.push(self.parse_expr()?);
|
||||
if !self.is_peek(TokenKind::Comma) {
|
||||
break;
|
||||
}
|
||||
self.advance(); // consume `,`
|
||||
}
|
||||
}
|
||||
|
||||
let rparen_token = self.expect(TokenKind::RParen)?;
|
||||
let span = lhs.span.join(rparen_token.span);
|
||||
|
||||
lhs = Expr {
|
||||
kind: ExprKind::Call {
|
||||
callee: Box::new(lhs),
|
||||
args,
|
||||
},
|
||||
ty: (),
|
||||
span,
|
||||
};
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some((op, left_bp, right_bp)) = self.infix_operator(peek_token.kind) else {
|
||||
break; // Not an infix operator
|
||||
};
|
||||
|
||||
@@ -251,6 +251,12 @@ impl Sema {
|
||||
params,
|
||||
return_type,
|
||||
..
|
||||
}
|
||||
| DeclKind::ForeignFunction {
|
||||
name,
|
||||
params,
|
||||
return_type,
|
||||
..
|
||||
} => {
|
||||
let param_tys: Vec<Ty> = params.iter().map(|p| Ty::from(&p.ty.kind)).collect();
|
||||
let ret_ty = return_type
|
||||
@@ -321,6 +327,31 @@ impl Sema {
|
||||
span: decl.span,
|
||||
}
|
||||
}
|
||||
DeclKind::ForeignFunction {
|
||||
name,
|
||||
name_span,
|
||||
params,
|
||||
return_type,
|
||||
} => {
|
||||
let typed_params = params
|
||||
.iter()
|
||||
.map(|p| (p.name.clone(), Ty::from(&p.ty.kind)))
|
||||
.collect();
|
||||
let expected_ret_ty = return_type
|
||||
.as_ref()
|
||||
.map(|t| Ty::from(&t.kind))
|
||||
.unwrap_or(Ty::Unit);
|
||||
|
||||
TypedDecl {
|
||||
kind: TypedDeclKind::ForeignFunction {
|
||||
name: name.clone(),
|
||||
name_span: *name_span,
|
||||
params: typed_params,
|
||||
return_type: expected_ret_ty,
|
||||
},
|
||||
span: decl.span,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -663,6 +694,33 @@ impl Sema {
|
||||
span: expr.span,
|
||||
}
|
||||
}
|
||||
ExprKind::Call { callee, args } => {
|
||||
let typed_callee = self.analyze_expr(callee);
|
||||
let mut typed_args = Vec::new();
|
||||
let mut arg_tys = Vec::new();
|
||||
|
||||
for arg in args {
|
||||
let typed_arg = self.analyze_expr(arg);
|
||||
arg_tys.push(typed_arg.ty.clone());
|
||||
typed_args.push(typed_arg);
|
||||
}
|
||||
|
||||
let ret_ty = self.new_var();
|
||||
let expected_callee_ty = Ty::Function(arg_tys, Box::new(ret_ty.clone()));
|
||||
|
||||
if let Err(e) = self.unify(&typed_callee.ty, &expected_callee_ty) {
|
||||
self.errors.push(SemanticError::new(e, callee.span));
|
||||
}
|
||||
|
||||
TypedExpr {
|
||||
kind: TypedExprKind::Call {
|
||||
callee: Box::new(typed_callee),
|
||||
args: typed_args,
|
||||
},
|
||||
ty: ret_ty,
|
||||
span: expr.span,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -690,6 +748,20 @@ impl Sema {
|
||||
body: self.apply_subst_stmt(body),
|
||||
}
|
||||
}
|
||||
TypedDeclKind::ForeignFunction {
|
||||
name,
|
||||
name_span,
|
||||
params,
|
||||
return_type,
|
||||
} => TypedDeclKind::ForeignFunction {
|
||||
name,
|
||||
name_span,
|
||||
params: params
|
||||
.into_iter()
|
||||
.map(|(n, ty)| (n, self.apply_subst(&ty)))
|
||||
.collect(),
|
||||
return_type: self.apply_subst(&return_type),
|
||||
},
|
||||
};
|
||||
|
||||
TypedDecl { kind, span }
|
||||
@@ -775,6 +847,10 @@ impl Sema {
|
||||
expr: Box::new(self.apply_subst_expr(*expr)),
|
||||
ty: self.apply_subst(&ty),
|
||||
},
|
||||
TypedExprKind::Call { callee, args } => TypedExprKind::Call {
|
||||
callee: Box::new(self.apply_subst_expr(*callee)),
|
||||
args: args.into_iter().map(|a| self.apply_subst_expr(a)).collect(),
|
||||
},
|
||||
};
|
||||
|
||||
TypedExpr { kind, ty, span }
|
||||
|
||||
@@ -57,6 +57,7 @@ pub enum TokenKind {
|
||||
|
||||
// Keywords
|
||||
Fn,
|
||||
Foreign,
|
||||
If,
|
||||
As,
|
||||
Else,
|
||||
@@ -121,6 +122,7 @@ impl Display for TokenKind {
|
||||
TokenKind::BooleanLit => "a boolean",
|
||||
TokenKind::FloatLit => "a float",
|
||||
TokenKind::Fn => "`fn`",
|
||||
TokenKind::Foreign => "`foreign`",
|
||||
TokenKind::As => "`as`",
|
||||
TokenKind::If => "`if`",
|
||||
TokenKind::Else => "`else`",
|
||||
|
||||
Reference in New Issue
Block a user