48098f83c1
Implement tuple expressions, types, and patterns, all the way through the parser down to the typechecker. In LLVM, these are implemented as anonymous structs, using an `extract` instruction when they're pattern matched on to get out the individual fields. Currently the only limitation here is patterns aren't supported in function argument position, but you can still do something like fn xy = let (x, y) = xy in x + y Change-Id: I357f17e9d4052e741eda8605b6662822f331efde Reviewed-on: https://cl.tvl.fyi/c/depot/+/3027 Reviewed-by: grfn <grfn@gws.fyi> Tested-by: BuildkiteCI
240 lines
5.9 KiB
Rust
240 lines
5.9 KiB
Rust
use nom::character::complete::{multispace0, multispace1};
|
|
use nom::error::{ErrorKind, ParseError};
|
|
use nom::{alt, char, complete, do_parse, eof, many0, named, separated_list0, tag, terminated};
|
|
|
|
#[macro_use]
|
|
pub(crate) mod macros;
|
|
mod expr;
|
|
mod type_;
|
|
mod util;
|
|
|
|
use crate::ast::{Arg, Decl, Fun, Ident};
|
|
pub use expr::expr;
|
|
use type_::function_type;
|
|
pub use type_::type_;
|
|
|
|
pub type Error = nom::Err<nom::error::Error<String>>;
|
|
|
|
pub(crate) fn is_reserved(s: &str) -> bool {
|
|
matches!(
|
|
s,
|
|
"if" | "then"
|
|
| "else"
|
|
| "let"
|
|
| "in"
|
|
| "fn"
|
|
| "ty"
|
|
| "int"
|
|
| "float"
|
|
| "bool"
|
|
| "true"
|
|
| "false"
|
|
| "cstring"
|
|
)
|
|
}
|
|
|
|
pub(crate) fn ident<'a, E>(i: &'a str) -> nom::IResult<&'a str, Ident, E>
|
|
where
|
|
E: ParseError<&'a str>,
|
|
{
|
|
let mut chars = i.chars();
|
|
if let Some(f) = chars.next() {
|
|
if f.is_alphabetic() || f == '_' {
|
|
let mut idx = 1;
|
|
for c in chars {
|
|
if !(c.is_alphanumeric() || c == '_') {
|
|
break;
|
|
}
|
|
idx += 1;
|
|
}
|
|
let id = &i[..idx];
|
|
if is_reserved(id) {
|
|
Err(nom::Err::Error(E::from_error_kind(i, ErrorKind::Satisfy)))
|
|
} else {
|
|
Ok((&i[idx..], Ident::from_str_unchecked(id)))
|
|
}
|
|
} else {
|
|
Err(nom::Err::Error(E::from_error_kind(i, ErrorKind::Satisfy)))
|
|
}
|
|
} else {
|
|
Err(nom::Err::Error(E::from_error_kind(i, ErrorKind::Eof)))
|
|
}
|
|
}
|
|
|
|
named!(ascripted_arg(&str) -> Arg, do_parse!(
|
|
complete!(char!('(')) >>
|
|
multispace0 >>
|
|
ident: ident >>
|
|
multispace0 >>
|
|
complete!(char!(':')) >>
|
|
multispace0 >>
|
|
type_: type_ >>
|
|
multispace0 >>
|
|
complete!(char!(')')) >>
|
|
(Arg {
|
|
ident,
|
|
type_: Some(type_)
|
|
})
|
|
));
|
|
|
|
named!(arg(&str) -> Arg, alt!(
|
|
ident => { |ident| Arg {ident, type_: None}} |
|
|
ascripted_arg
|
|
));
|
|
|
|
named!(extern_decl(&str) -> Decl, do_parse!(
|
|
complete!(tag!("extern"))
|
|
>> multispace1
|
|
>> name: ident
|
|
>> multispace0
|
|
>> char!(':')
|
|
>> multispace0
|
|
>> type_: function_type
|
|
>> multispace0
|
|
>> (Decl::Extern {
|
|
name,
|
|
type_
|
|
})
|
|
));
|
|
|
|
named!(fun_decl(&str) -> Decl, do_parse!(
|
|
complete!(tag!("fn"))
|
|
>> multispace1
|
|
>> name: ident
|
|
>> multispace1
|
|
>> args: separated_list0!(multispace1, arg)
|
|
>> multispace0
|
|
>> char!('=')
|
|
>> multispace0
|
|
>> body: expr
|
|
>> (Decl::Fun {
|
|
name,
|
|
body: Fun {
|
|
args,
|
|
body
|
|
}
|
|
})
|
|
));
|
|
|
|
named!(ascription_decl(&str) -> Decl, do_parse!(
|
|
complete!(tag!("ty"))
|
|
>> multispace1
|
|
>> name: ident
|
|
>> multispace0
|
|
>> complete!(char!(':'))
|
|
>> multispace0
|
|
>> type_: type_
|
|
>> multispace0
|
|
>> (Decl::Ascription {
|
|
name,
|
|
type_
|
|
})
|
|
));
|
|
|
|
named!(pub decl(&str) -> Decl, alt!(
|
|
ascription_decl |
|
|
fun_decl |
|
|
extern_decl
|
|
));
|
|
|
|
named!(pub toplevel(&str) -> Vec<Decl>, do_parse!(
|
|
decls: many0!(decl)
|
|
>> multispace0
|
|
>> eof!()
|
|
>> (decls)));
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::convert::TryInto;
|
|
|
|
use crate::ast::{BinaryOperator, Expr, FunctionType, Literal, Type};
|
|
|
|
use super::*;
|
|
use expr::tests::ident_expr;
|
|
|
|
#[test]
|
|
fn fn_decl() {
|
|
let res = test_parse!(decl, "fn id x = x");
|
|
assert_eq!(
|
|
res,
|
|
Decl::Fun {
|
|
name: "id".try_into().unwrap(),
|
|
body: Fun {
|
|
args: vec!["x".try_into().unwrap()],
|
|
body: *ident_expr("x"),
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn ascripted_fn_args() {
|
|
test_parse!(ascripted_arg, "(x : int)");
|
|
let res = test_parse!(decl, "fn plus1 (x : int) = x + 1");
|
|
assert_eq!(
|
|
res,
|
|
Decl::Fun {
|
|
name: "plus1".try_into().unwrap(),
|
|
body: Fun {
|
|
args: vec![Arg {
|
|
ident: "x".try_into().unwrap(),
|
|
type_: Some(Type::Int),
|
|
}],
|
|
body: Expr::BinaryOp {
|
|
lhs: ident_expr("x"),
|
|
op: BinaryOperator::Add,
|
|
rhs: Box::new(Expr::Literal(Literal::Int(1))),
|
|
}
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn multiple_decls() {
|
|
let res = test_parse!(
|
|
toplevel,
|
|
"fn id x = x
|
|
fn plus x y = x + y
|
|
fn main = plus (id 2) 7"
|
|
);
|
|
assert_eq!(res.len(), 3);
|
|
let res = test_parse!(
|
|
toplevel,
|
|
"fn id x = x\nfn plus x y = x + y\nfn main = plus (id 2) 7\n"
|
|
);
|
|
assert_eq!(res.len(), 3);
|
|
}
|
|
|
|
#[test]
|
|
fn top_level_ascription() {
|
|
let res = test_parse!(toplevel, "ty id : fn a -> a");
|
|
assert_eq!(
|
|
res,
|
|
vec![Decl::Ascription {
|
|
name: "id".try_into().unwrap(),
|
|
type_: Type::Function(FunctionType {
|
|
args: vec![Type::Var("a".try_into().unwrap())],
|
|
ret: Box::new(Type::Var("a".try_into().unwrap()))
|
|
})
|
|
}]
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn return_unit() {
|
|
assert_eq!(
|
|
test_parse!(decl, "fn g _ = ()"),
|
|
Decl::Fun {
|
|
name: "g".try_into().unwrap(),
|
|
body: Fun {
|
|
args: vec![Arg {
|
|
ident: "_".try_into().unwrap(),
|
|
type_: None,
|
|
}],
|
|
body: Expr::Literal(Literal::Unit),
|
|
},
|
|
}
|
|
)
|
|
}
|
|
}
|