Skip to content

Commit

Permalink
Record and tuple patterns, new pattern match compiler
Browse files Browse the repository at this point in the history
  • Loading branch information
Kmeakin committed Jan 30, 2023
1 parent 98711d0 commit 3917860
Show file tree
Hide file tree
Showing 58 changed files with 2,334 additions and 894 deletions.
6 changes: 3 additions & 3 deletions doc/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,14 @@
- [ ] refinement types
- [x] match expressions
- [x] single-layer pattern matching
- [ ] multi-layer pattern matching
- [x] multi-layer pattern matching
- [ ] dependent pattern matching
- [ ] patterns
- [x] patterns
- [x] wildcard patterns
- [x] named patterns
- [x] annotated patterns
- [x] numeric literal patterns
- [ ] record literal patterns
- [x] record literal patterns
- [ ] invertible format descriptions

## Implementation
Expand Down
172 changes: 171 additions & 1 deletion fathom/src/core.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
use std::fmt;

use crate::env::{Index, Level};
use scoped_arena::Scope;

use crate::env::{EnvLen, Index, Level};
use crate::source::{Span, StringId};

pub mod binary;
Expand Down Expand Up @@ -205,6 +207,10 @@ pub enum Term<'arena> {
}

impl<'arena> Term<'arena> {
pub fn error(span: impl Into<Span>) -> Self {
Self::Prim(span.into(), Prim::ReportedError)
}

/// Get the source span of the term.
pub fn span(&self) -> Span {
match self {
Expand Down Expand Up @@ -279,6 +285,155 @@ impl<'arena> Term<'arena> {
pub fn is_error(&self) -> bool {
matches!(self, Term::Prim(_, Prim::ReportedError))
}

// TODO: Add a new `Weaken` variant to `core::Term` instead of eagerly
// traversing the term? See [Andras Kovacs’ staged language](https://github.com/AndrasKovacs/staged/blob/9e381eb162f44912d70fb843c4ca6567b0d1683a/demo/Syntax.hs#L52) for an example
pub fn shift(&self, scope: &'arena Scope<'arena>, amount: EnvLen) -> Term<'arena> {
self.shift_inner(scope, Index::last(), amount)
}

/// Increment all `LocalVar`s greater than or equal to `min` by `amount`
fn shift_inner(
&self,
scope: &'arena Scope<'arena>,
mut min: Index,
amount: EnvLen,
) -> Term<'arena> {
// Skip traversing and rebuilding the term if it would make no change. Increases
// sharing.
if amount == EnvLen::new() {
return self.clone();
}

match self {
Term::LocalVar(span, var) if *var >= min => Term::LocalVar(*span, *var + amount),
Term::LocalVar(..)
| Term::ItemVar(..)
| Term::MetaVar(..)
| Term::InsertedMeta(..)
| Term::Prim(..)
| Term::ConstLit(..)
| Term::Universe(..) => self.clone(),
Term::Ann(span, expr, r#type) => Term::Ann(
*span,
scope.to_scope(expr.shift_inner(scope, min, amount)),
scope.to_scope(r#type.shift_inner(scope, min, amount)),
),
Term::Let(span, name, def_type, def_expr, body) => Term::Let(
*span,
*name,
scope.to_scope(def_type.shift_inner(scope, min, amount)),
scope.to_scope(def_expr.shift_inner(scope, min, amount)),
scope.to_scope(body.shift_inner(scope, min.prev(), amount)),
),
Term::FunType(span, plicity, name, input, output) => Term::FunType(
*span,
*plicity,
*name,
scope.to_scope(input.shift_inner(scope, min, amount)),
scope.to_scope(output.shift_inner(scope, min.prev(), amount)),
),
Term::FunLit(span, plicity, name, body) => Term::FunLit(
*span,
*plicity,
*name,
scope.to_scope(body.shift_inner(scope, min.prev(), amount)),
),
Term::FunApp(span, plicity, fun, arg) => Term::FunApp(
*span,
*plicity,
scope.to_scope(fun.shift_inner(scope, min, amount)),
scope.to_scope(arg.shift_inner(scope, min, amount)),
),
Term::RecordType(span, labels, types) => Term::RecordType(
*span,
labels,
scope.to_scope_from_iter(types.iter().map(|r#type| {
let ret = r#type.shift_inner(scope, min, amount);
min = min.prev();
ret
})),
),
Term::RecordLit(span, labels, exprs) => Term::RecordLit(
*span,
labels,
scope.to_scope_from_iter(
exprs
.iter()
.map(|expr| expr.shift_inner(scope, min, amount)),
),
),
Term::RecordProj(span, head, label) => Term::RecordProj(
*span,
scope.to_scope(head.shift_inner(scope, min, amount)),
*label,
),
Term::ArrayLit(span, terms) => Term::ArrayLit(
*span,
scope.to_scope_from_iter(
terms
.iter()
.map(|term| term.shift_inner(scope, min, amount)),
),
),
Term::FormatRecord(span, labels, terms) => Term::FormatRecord(
*span,
labels,
scope.to_scope_from_iter(terms.iter().map(|term| {
let ret = term.shift_inner(scope, min, amount);
min = min.prev();
ret
})),
),
Term::FormatCond(span, name, format, pred) => Term::FormatCond(
*span,
*name,
scope.to_scope(format.shift_inner(scope, min, amount)),
scope.to_scope(pred.shift_inner(scope, min.prev(), amount)),
),
Term::FormatOverlap(span, labels, terms) => Term::FormatOverlap(
*span,
labels,
scope.to_scope_from_iter(terms.iter().map(|term| {
let ret = term.shift_inner(scope, min, amount);
min = min.prev();
ret
})),
),
Term::ConstMatch(span, scrut, branches, default) => Term::ConstMatch(
*span,
scope.to_scope(scrut.shift_inner(scope, min, amount)),
scope.to_scope_from_iter(
branches
.iter()
.map(|(r#const, term)| (*r#const, term.shift_inner(scope, min, amount))),
),
default.map(|(name, term)| {
(
name,
scope.to_scope(term.shift_inner(scope, min.prev(), amount)) as &_,
)
}),
),
}
}

/// Returns `true` if `self` can be evaluated in a single step.
/// Used as a heuristic to prevent increase in runtime when expanding
/// pattern matches
pub fn is_atomic(&self) -> bool {
match self {
Term::ItemVar(_, _)
| Term::LocalVar(_, _)
| Term::MetaVar(_, _)
| Term::InsertedMeta(_, _, _)
| Term::Universe(_)
| Term::Prim(_, _)
| Term::ConstLit(_, _) => true,
Term::RecordProj(_, head, _) => head.is_atomic(),
_ => false,
}
}
}

macro_rules! def_prims {
Expand Down Expand Up @@ -599,6 +754,21 @@ pub enum Const {
Ref(usize),
}

impl Const {
/// Return the number of inhabitants of `self`.
/// `None` represents infinity
pub fn num_inhabitants(&self) -> Option<u128> {
match self {
Const::Bool(_) => Some(2),
Const::U8(_, _) | Const::S8(_) => Some(1 << 8),
Const::U16(_, _) | Const::S16(_) => Some(1 << 16),
Const::U32(_, _) | Const::S32(_) => Some(1 << 32),
Const::U64(_, _) | Const::S64(_) => Some(1 << 64),
Const::F32(_) | Const::F64(_) | Const::Pos(_) | Const::Ref(_) => None,
}
}
}

impl PartialEq for Const {
fn eq(&self, other: &Self) -> bool {
match (*self, *other) {
Expand Down
9 changes: 9 additions & 0 deletions fathom/src/core/semantics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ pub enum Value<'arena> {
}

impl<'arena> Value<'arena> {
pub const ERROR: Self = Self::Stuck(Head::Prim(Prim::ReportedError), Vec::new());

pub fn prim(prim: Prim, params: impl IntoIterator<Item = ArcValue<'arena>>) -> Value<'arena> {
let params = params
.into_iter()
Expand All @@ -76,6 +78,13 @@ impl<'arena> Value<'arena> {
}
}

pub fn match_record_type(&self) -> Option<&Telescope<'arena>> {
match self {
Value::RecordType(_, telescope) => Some(telescope),
_ => None,
}
}

pub fn is_error(&self) -> bool {
matches!(self, Value::Stuck(Head::Prim(Prim::ReportedError), _))
}
Expand Down
19 changes: 19 additions & 0 deletions fathom/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
//! [`SharedEnv`] to increase the amount of sharing at the expense of locality.
use std::fmt;
use std::ops::Add;

/// Underlying variable representation.
type RawVar = u16;
Expand Down Expand Up @@ -56,6 +57,13 @@ impl Index {
}
}

impl Add<EnvLen> for Index {
type Output = Self;
fn add(self, rhs: EnvLen) -> Self::Output {
Self(self.0 + rhs.0) // FIXME: check overflow?
}
}

impl fmt::Debug for Index {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Index(")?;
Expand Down Expand Up @@ -126,6 +134,13 @@ pub fn levels() -> impl Iterator<Item = Level> {
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct EnvLen(RawVar);

impl Add<Index> for EnvLen {
type Output = Self;
fn add(self, rhs: Index) -> Self::Output {
Self(self.0 + rhs.0) // FIXME: check overflow?
}
}

impl EnvLen {
/// Construct a new, empty environment.
pub fn new() -> EnvLen {
Expand All @@ -152,6 +167,10 @@ impl EnvLen {
Level(self.0)
}

pub fn next(&self) -> EnvLen {
Self(self.0 + 1) // FIXME: check overflow?
}

/// Push an entry onto the environment.
pub fn push(&mut self) {
self.0 += 1; // FIXME: check overflow?
Expand Down
35 changes: 24 additions & 11 deletions fathom/src/surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ pub struct ItemDef<'arena, Range> {

/// Surface patterns.
#[derive(Debug, Clone)]
pub enum Pattern<Range> {
pub enum Pattern<'arena, Range> {
/// Named patterns, eg. `x`, `true`, `false`
Name(Range, StringId),
/// Placeholder patterns, eg. `_`
Expand All @@ -93,8 +93,10 @@ pub enum Pattern<Range> {
NumberLiteral(Range, StringId),
/// Boolean literal patterns
BooleanLiteral(Range, bool),
// TODO: Record literal patterns
// RecordLiteral(Range, &'arena [((Range, StringId), Pattern<'arena, Range>)]),
/// Record literal patterns
RecordLiteral(Range, &'arena [PatternField<'arena, Range>]),
/// Tuple literal patterns
TupleLiteral(Range, &'arena [Pattern<'arena, Range>]),
}

#[derive(Debug, Clone, Copy)]
Expand Down Expand Up @@ -167,14 +169,16 @@ impl<Range> fmt::Display for BinOp<Range> {
}
}

impl<Range: Clone> Pattern<Range> {
impl<'arena, Range: Clone> Pattern<'arena, Range> {
pub fn range(&self) -> Range {
match self {
Pattern::Name(range, _)
| Pattern::Placeholder(range)
| Pattern::StringLiteral(range, _)
| Pattern::NumberLiteral(range, _)
| Pattern::BooleanLiteral(range, _) => range.clone(),
| Pattern::BooleanLiteral(range, _)
| Pattern::RecordLiteral(range, _)
| Pattern::TupleLiteral(range, _) => range.clone(),
}
}
}
Expand All @@ -199,7 +203,7 @@ pub enum Term<'arena, Range> {
/// Let expressions.
Let(
Range,
Pattern<Range>,
&'arena Pattern<'arena, Range>,
Option<&'arena Term<'arena, Range>>,
&'arena Term<'arena, Range>,
&'arena Term<'arena, Range>,
Expand All @@ -215,7 +219,7 @@ pub enum Term<'arena, Range> {
Match(
Range,
&'arena Term<'arena, Range>,
&'arena [(Pattern<Range>, Term<'arena, Range>)],
&'arena [(Pattern<'arena, Range>, Term<'arena, Range>)],
),
/// The type of types.
Universe(Range),
Expand Down Expand Up @@ -352,7 +356,7 @@ impl<'arena> Term<'arena, FileRange> {
#[derive(Debug, Clone)]
pub struct Param<'arena, Range> {
pub plicity: Plicity,
pub pattern: Pattern<Range>,
pub pattern: Pattern<'arena, Range>,
pub r#type: Option<Term<'arena, Range>>,
}

Expand Down Expand Up @@ -403,6 +407,15 @@ pub struct ExprField<'arena, Range> {
expr: Term<'arena, Range>,
}

/// A field definition in a record pattern
#[derive(Debug, Clone)]
pub struct PatternField<'arena, Range> {
/// Label identifying the field
label: (Range, StringId),
/// The pattern that this field will match
pattern: Pattern<'arena, Range>,
}

/// Messages produced during parsing
#[derive(Clone, Debug)]
pub enum ParseMessage {
Expand Down Expand Up @@ -529,14 +542,14 @@ mod tests {
#[test]
#[cfg(target_pointer_width = "64")]
fn term_size() {
assert_eq!(std::mem::size_of::<Term<()>>(), 32);
assert_eq!(std::mem::size_of::<Term<()>>(), 40);
assert_eq!(std::mem::size_of::<Term<ByteRange>>(), 48);
}

#[test]
#[cfg(target_pointer_width = "64")]
fn pattern_size() {
assert_eq!(std::mem::size_of::<Pattern<()>>(), 8);
assert_eq!(std::mem::size_of::<Pattern<ByteRange>>(), 16);
assert_eq!(std::mem::size_of::<Pattern<()>>(), 24);
assert_eq!(std::mem::size_of::<Pattern<ByteRange>>(), 32);
}
}
Loading

0 comments on commit 3917860

Please sign in to comment.