Compare commits

..

2 commits

Author SHA1 Message Date
Alex Bethel 01a6a5bbba Add typeck Type type 2022-08-12 21:33:35 -06:00
Alex Bethel cd0353b31a Add dummy typ values on expressions in the AST 2022-08-12 21:23:26 -06:00
4 changed files with 130 additions and 62 deletions

View file

@ -2,13 +2,13 @@
use std::{error::Error, fmt::Display, fs::File, io::Write, process::exit, str::FromStr};
use clap::Parser;
use drimc_rs::{
ast2ir::ast2ir,
backends,
parser::{parser, ParserError, ParserMeta},
typeck::typeck,
};
use clap::Parser;
/// Optimization levels.
#[derive(Debug)]
@ -256,7 +256,7 @@ fn main() {
let source = std::fs::read_to_string(&args.source_file)?;
let meta = ParserMeta::default();
let ast = chumsky::Parser::parse(&parser(&meta), source).map_err(ParserError)?;
typeck(&ast)?;
let ast = typeck(ast)?;
let ir = ast2ir(ast);

View file

@ -10,7 +10,8 @@ use chumsky::{
};
use crate::syntax::{
ClassMember, Expr, Identifier, Literal, Pattern, Statement, SyntaxTree, Type, TypeConstructor,
ClassMember, Expr, ExprKind, Identifier, Literal, Pattern, Statement, SyntaxTree, Type,
TypeConstructor,
};
/// Adapter to make `chumsky`'s parser errors usable as standard Rust errors.
@ -330,6 +331,7 @@ fn parse_func_decl<'a>(
name,
arguments,
definition,
typ: None,
})
}
@ -372,15 +374,15 @@ fn parse_expression<'a>(
let application = term.repeated().at_least(1).map(|exprs| {
exprs
.into_iter()
.reduce(|l, r| Expr::Application {
.reduce(|l, r| {
expr(ExprKind::Application {
func: Box::new(l),
argument: Box::new(r),
})
})
.unwrap()
});
// let unary = parse_unary(m, term);
// let unary = term;
let unary = parse_unary(m, application);
let binary = (0..=10).rev().fold(unary.boxed(), |p, precedence| {
@ -399,12 +401,14 @@ fn parse_unary(
.repeated()
.then(base)
.map(|(ops, exp)| {
ops.into_iter().fold(exp, |exp, op| Expr::UnaryOp {
ops.into_iter().fold(exp, |exp, op| {
expr(ExprKind::UnaryOp {
kind: op.to_string(),
val: Box::new(exp),
translation: "negate".to_string(),
})
})
})
}
fn parse_binary<'a>(
@ -442,12 +446,12 @@ fn parse_binary<'a>(
others
.into_iter()
.fold(first, |left, (op_name, _assoc, translation, right)| {
Expr::BinaryOp {
expr(ExprKind::BinaryOp {
kind: op_name.to_owned(),
left: Box::new(left),
right: Box::new(right),
translation: translation.to_string(),
}
})
})
}
Some(Associativity::Right) => {
@ -488,12 +492,14 @@ fn parse_binary<'a>(
others_l
.into_iter()
.rev()
.fold(last.to_owned(), |r, (l, (op, trans))| Expr::BinaryOp {
.fold(last.to_owned(), |r, (l, (op, trans))| {
expr(ExprKind::BinaryOp {
kind: op.to_string(),
left: Box::new(l.to_owned()),
right: Box::new(r),
translation: trans.to_string(),
})
})
}
}
})
@ -501,66 +507,72 @@ fn parse_binary<'a>(
fn parse_let_expr(
m: &ParserMeta,
base: impl Parser<char, Expr, Error = Simple<char>> + Clone,
rec: impl Parser<char, Expr, Error = Simple<char>> + Clone,
) -> impl Parser<char, Expr, Error = Simple<char>> + Clone {
pad(keyword("let"))
.ignore_then(parse_pattern(m))
.then_ignore(pad(just('=')))
.then(base.clone())
.then(rec.clone())
.then_ignore(pad(keyword("in")))
.then(base)
.map(|((left, right), into)| Expr::Let {
.then(rec)
.map(|((left, right), into)| {
expr(ExprKind::Let {
left,
right: Box::new(right),
into: Box::new(into),
})
})
}
fn parse_match_expr(
m: &ParserMeta,
base: impl Parser<char, Expr, Error = Simple<char>> + Clone,
rec: impl Parser<char, Expr, Error = Simple<char>> + Clone,
) -> impl Parser<char, Expr, Error = Simple<char>> + Clone {
pad(keyword("match"))
.ignore_then(base.clone())
.ignore_then(rec.clone())
.then(
parse_pattern(m)
.then_ignore(pad(just("=>")))
.then(base)
.then(rec)
.separated_by(pad(just(",")))
.allow_trailing()
.delimited_by(pad(just('{')), pad(just('}'))),
)
.map(|(matcher, cases)| Expr::Match {
.map(|(matcher, cases)| {
expr(ExprKind::Match {
matcher: Box::new(matcher),
cases,
})
})
}
fn parse_record_expr(
_m: &ParserMeta,
base: impl Parser<char, Expr, Error = Simple<char>> + Clone,
rec: impl Parser<char, Expr, Error = Simple<char>> + Clone,
) -> impl Parser<char, Expr, Error = Simple<char>> + Clone {
pad(ident())
.then_ignore(pad(just(':')))
.then(base)
.then(rec)
.separated_by(pad(just(',')))
.allow_trailing()
.delimited_by(pad(just('{')), pad(just('}')))
.map(Expr::Record)
.map(|elems| expr(ExprKind::Record(elems)))
}
fn parse_lambda_expr(
m: &ParserMeta,
base: impl Parser<char, Expr, Error = Simple<char>> + Clone,
rec: impl Parser<char, Expr, Error = Simple<char>> + Clone,
) -> impl Parser<char, Expr, Error = Simple<char>> + Clone {
pad(keyword("fn"))
.ignore_then(parse_pattern(m).repeated())
.then_ignore(pad(just("->")))
.then(base)
.map(|(arguments, result)| Expr::Lambda {
.then(rec)
.map(|(arguments, result)| {
expr(ExprKind::Lambda {
arguments,
result: Box::new(result),
})
})
}
fn parse_subscript_expr(
@ -585,36 +597,38 @@ fn parse_subscript_expr(
.repeated(),
)
.map(|(l, subscripts): (Expr, Vec<(SubscriptKind, Expr)>)| {
subscripts.into_iter().fold(l, |l, (kind, r)| match kind {
SubscriptKind::Dot => Expr::DotSubscript {
subscripts.into_iter().fold(l, |l, (kind, r)| {
expr(match kind {
SubscriptKind::Dot => ExprKind::DotSubscript {
value: Box::new(l),
subscript: Box::new(r),
},
SubscriptKind::Bracket => Expr::BracketSubscript {
SubscriptKind::Bracket => ExprKind::BracketSubscript {
value: Box::new(l),
subscript: Box::new(r),
},
})
})
})
}
fn parse_tuple_expr(
_m: &ParserMeta,
base: impl Parser<char, Expr, Error = Simple<char>> + Clone,
rec: impl Parser<char, Expr, Error = Simple<char>> + Clone,
) -> impl Parser<char, Expr, Error = Simple<char>> + Clone {
base.separated_by(pad(just(',')))
rec.separated_by(pad(just(',')))
.delimited_by(pad(just('(')), pad(just(')')))
.map(|exprs| {
if exprs.len() == 1 {
exprs.into_iter().next().unwrap()
} else {
Expr::Tuple(exprs)
expr(ExprKind::Tuple(exprs))
}
})
}
fn parse_var_ref_expr(m: &ParserMeta) -> impl Parser<char, Expr, Error = Simple<char>> + Clone {
parse_identifier(m).map(Expr::VariableReference)
parse_identifier(m).map(|r| expr(ExprKind::VariableReference(r)))
}
fn parse_literal(_m: &ParserMeta) -> impl Parser<char, Expr, Error = Simple<char>> + Clone {
@ -659,7 +673,7 @@ fn parse_literal(_m: &ParserMeta) -> impl Parser<char, Expr, Error = Simple<char
.map(|(l, r)| (l + "." + &r).parse().unwrap())
.map(Literal::Float);
pad(choice((int, float, string))).map(Expr::Literal)
pad(choice((int, float, string))).map(|lit| expr(ExprKind::Literal(lit)))
}
fn parse_identifier(
@ -797,8 +811,8 @@ fn parse_record_pattern(
fn parse_literal_pattern(m: &ParserMeta) -> impl Parser<char, Pattern, Error = Simple<char>> {
// TODO: factor out literal parsing so we don't have to do this ugly `unreachable` stuff.
parse_literal(m).map(|e| match e {
Expr::Literal(lit) => Pattern::Literal(lit),
parse_literal(m).map(|e| match e.kind {
ExprKind::Literal(lit) => Pattern::Literal(lit),
_ => unreachable!(),
})
}
@ -829,6 +843,10 @@ fn ident() -> impl Parser<char, String, Error = Simple<char>> + Clone {
})
}
fn expr(e: ExprKind) -> Expr {
Expr { kind: e, typ: None }
}
#[cfg(test)]
mod tests {
use super::*;

View file

@ -2,6 +2,8 @@
use num_bigint::BigUint;
use crate::typeck;
/// A concrete syntax tree. This represents the full content of a Drim program, including all
/// whitespace, comments, and tokens: the source code of the original program can be recovered
/// completely using the syntax tree.
@ -65,6 +67,10 @@ pub enum ClassMember {
/// The definition of the function.
definition: Option<Expr>,
/// The type of the overall function; this is filled in by the typechecker, and is left
/// blank by the parser.
typ: Option<typeck::Type>,
},
/// Declaration of a type that is a literal alias for another type.
@ -87,9 +93,19 @@ pub struct TypeConstructor {
pub args: Vec<Type>,
}
/// Expressions.
/// An expression.
#[derive(Clone, Debug)]
pub enum Expr {
pub struct Expr {
/// The contents of the expression.
pub kind: ExprKind,
/// An optional type signature, left as `None` by the parser and added by the type checker.
pub typ: Option<typeck::Type>,
}
/// The different kinds of expressions.
#[derive(Clone, Debug)]
pub enum ExprKind {
/// Unary operators, e.g., `-5`.
UnaryOp {
/// The text of the operator.
@ -252,7 +268,7 @@ pub enum Pattern {
}
/// Namespaced identifiers.
#[derive(Clone, Debug)]
#[derive(Clone, Debug, Hash, PartialEq)]
pub struct Identifier {
/// The elements of the identifier; there must be at least one of these.
pub elems: Vec<String>,

View file

@ -1,8 +1,10 @@
//! Type checker.
use std::{error::Error, fmt::Display};
use std::{collections::BTreeMap, error::Error, fmt::Display};
use crate::syntax::SyntaxTree;
use num_bigint::BigInt;
use crate::syntax::{Identifier, SyntaxTree};
/// A compile-time type error from the user's source code.
#[derive(Debug)]
@ -16,7 +18,39 @@ impl Display for TypeError {
impl Error for TypeError {}
/// A type known at compile time. While this resembles the AST `Type` structure, this enum is
/// optimized for unifying types against one another and representing compiler-generated types
/// rather than strictly representing named types.
#[derive(Debug, Clone, PartialEq)]
pub enum Type {
/// `Foo`
Named(Identifier),
/// `List Int`
Application {
/// The function being applied. This must be a generic type.
function: Box<Type>,
/// The type given as an argument to the type.
expression: Box<Type>,
},
/// `(a, b)`
Tuple(Vec<Type>),
/// `{ a: x, b: y }`
Record(BTreeMap<String, Type>),
/// Compiler-internal type representing an arbitrary-precision integer whose value is known at
/// compile time. This is the default type of integer literals. A `CompInt` can be converted to
/// an actual integer type via implicit application of the `fromCompInt` generic function.
CompInt(BigInt),
/// Compiler-internal type representing a string literal. See `CompInt`.
CompString(String),
}
/// Type-checks the syntax tree.
pub fn typeck(_: &SyntaxTree) -> Result<(), TypeError> {
pub fn typeck(_: SyntaxTree) -> Result<SyntaxTree, TypeError> {
todo!()
}