Skip to content

Commit 8e82b43

Browse files
committed
Record and tuple patterns, new pattern match compiler
1 parent 6926f6a commit 8e82b43

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+2302
-877
lines changed

doc/roadmap.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,14 @@
3939
- [ ] refinement types
4040
- [x] match expressions
4141
- [x] single-layer pattern matching
42-
- [ ] multi-layer pattern matching
42+
- [x] multi-layer pattern matching
4343
- [ ] dependent pattern matching
44-
- [ ] patterns
44+
- [x] patterns
4545
- [x] wildcard patterns
4646
- [x] named patterns
4747
- [x] annotated patterns
4848
- [x] numeric literal patterns
49-
- [ ] record literal patterns
49+
- [x] record literal patterns
5050
- [ ] invertible format descriptions
5151

5252
## Implementation

fathom/src/core.rs

Lines changed: 171 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
33
use std::fmt;
44

5-
use crate::env::{Index, Level};
5+
use scoped_arena::Scope;
6+
7+
use crate::env::{EnvLen, Index, Level};
68
use crate::source::{Span, StringId};
79

810
pub mod binary;
@@ -205,6 +207,10 @@ pub enum Term<'arena> {
205207
}
206208

207209
impl<'arena> Term<'arena> {
210+
pub fn error(span: impl Into<Span>) -> Self {
211+
Self::Prim(span.into(), Prim::ReportedError)
212+
}
213+
208214
/// Get the source span of the term.
209215
pub fn span(&self) -> Span {
210216
match self {
@@ -279,6 +285,155 @@ impl<'arena> Term<'arena> {
279285
pub fn is_error(&self) -> bool {
280286
matches!(self, Term::Prim(_, Prim::ReportedError))
281287
}
288+
289+
// TODO: Add a new `Weaken` variant to `core::Term` instead of eagerly
290+
// traversing the term? See [Andras Kovacs’ staged language](https://github.com/AndrasKovacs/staged/blob/9e381eb162f44912d70fb843c4ca6567b0d1683a/demo/Syntax.hs#L52) for an example
291+
pub fn shift(&self, scope: &'arena Scope<'arena>, amount: EnvLen) -> Term<'arena> {
292+
self.shift_inner(scope, Index::last(), amount)
293+
}
294+
295+
/// Increment all `LocalVar`s greater than or equal to `min` by `amount`
296+
fn shift_inner(
297+
&self,
298+
scope: &'arena Scope<'arena>,
299+
mut min: Index,
300+
amount: EnvLen,
301+
) -> Term<'arena> {
302+
// Skip traversing and rebuilding the term if it would make no change. Increases
303+
// sharing.
304+
if amount == EnvLen::new() {
305+
return self.clone();
306+
}
307+
308+
match self {
309+
Term::LocalVar(span, var) if *var >= min => Term::LocalVar(*span, *var + amount),
310+
Term::LocalVar(..)
311+
| Term::ItemVar(..)
312+
| Term::MetaVar(..)
313+
| Term::InsertedMeta(..)
314+
| Term::Prim(..)
315+
| Term::ConstLit(..)
316+
| Term::Universe(..) => self.clone(),
317+
Term::Ann(span, expr, r#type) => Term::Ann(
318+
*span,
319+
scope.to_scope(expr.shift_inner(scope, min, amount)),
320+
scope.to_scope(r#type.shift_inner(scope, min, amount)),
321+
),
322+
Term::Let(span, name, def_type, def_expr, body) => Term::Let(
323+
*span,
324+
*name,
325+
scope.to_scope(def_type.shift_inner(scope, min, amount)),
326+
scope.to_scope(def_expr.shift_inner(scope, min, amount)),
327+
scope.to_scope(body.shift_inner(scope, min.prev(), amount)),
328+
),
329+
Term::FunType(span, plicity, name, input, output) => Term::FunType(
330+
*span,
331+
*plicity,
332+
*name,
333+
scope.to_scope(input.shift_inner(scope, min, amount)),
334+
scope.to_scope(output.shift_inner(scope, min.prev(), amount)),
335+
),
336+
Term::FunLit(span, plicity, name, body) => Term::FunLit(
337+
*span,
338+
*plicity,
339+
*name,
340+
scope.to_scope(body.shift_inner(scope, min.prev(), amount)),
341+
),
342+
Term::FunApp(span, plicity, fun, arg) => Term::FunApp(
343+
*span,
344+
*plicity,
345+
scope.to_scope(fun.shift_inner(scope, min, amount)),
346+
scope.to_scope(arg.shift_inner(scope, min, amount)),
347+
),
348+
Term::RecordType(span, labels, types) => Term::RecordType(
349+
*span,
350+
labels,
351+
scope.to_scope_from_iter(types.iter().map(|r#type| {
352+
let ret = r#type.shift_inner(scope, min, amount);
353+
min = min.prev();
354+
ret
355+
})),
356+
),
357+
Term::RecordLit(span, labels, exprs) => Term::RecordLit(
358+
*span,
359+
labels,
360+
scope.to_scope_from_iter(
361+
exprs
362+
.iter()
363+
.map(|expr| expr.shift_inner(scope, min, amount)),
364+
),
365+
),
366+
Term::RecordProj(span, head, label) => Term::RecordProj(
367+
*span,
368+
scope.to_scope(head.shift_inner(scope, min, amount)),
369+
*label,
370+
),
371+
Term::ArrayLit(span, terms) => Term::ArrayLit(
372+
*span,
373+
scope.to_scope_from_iter(
374+
terms
375+
.iter()
376+
.map(|term| term.shift_inner(scope, min, amount)),
377+
),
378+
),
379+
Term::FormatRecord(span, labels, terms) => Term::FormatRecord(
380+
*span,
381+
labels,
382+
scope.to_scope_from_iter(terms.iter().map(|term| {
383+
let ret = term.shift_inner(scope, min, amount);
384+
min = min.prev();
385+
ret
386+
})),
387+
),
388+
Term::FormatCond(span, name, format, pred) => Term::FormatCond(
389+
*span,
390+
*name,
391+
scope.to_scope(format.shift_inner(scope, min, amount)),
392+
scope.to_scope(pred.shift_inner(scope, min.prev(), amount)),
393+
),
394+
Term::FormatOverlap(span, labels, terms) => Term::FormatOverlap(
395+
*span,
396+
labels,
397+
scope.to_scope_from_iter(terms.iter().map(|term| {
398+
let ret = term.shift_inner(scope, min, amount);
399+
min = min.prev();
400+
ret
401+
})),
402+
),
403+
Term::ConstMatch(span, scrut, branches, default) => Term::ConstMatch(
404+
*span,
405+
scope.to_scope(scrut.shift_inner(scope, min, amount)),
406+
scope.to_scope_from_iter(
407+
branches
408+
.iter()
409+
.map(|(r#const, term)| (*r#const, term.shift_inner(scope, min, amount))),
410+
),
411+
default.map(|(name, term)| {
412+
(
413+
name,
414+
scope.to_scope(term.shift_inner(scope, min.prev(), amount)) as &_,
415+
)
416+
}),
417+
),
418+
}
419+
}
420+
421+
/// Returns `true` if `self` can be evaluated in a single step.
422+
/// Used as a heuristic to prevent increase in runtime when expanding
423+
/// pattern matches
424+
pub fn is_atomic(&self) -> bool {
425+
match self {
426+
Term::ItemVar(_, _)
427+
| Term::LocalVar(_, _)
428+
| Term::MetaVar(_, _)
429+
| Term::InsertedMeta(_, _, _)
430+
| Term::Universe(_)
431+
| Term::Prim(_, _)
432+
| Term::ConstLit(_, _) => true,
433+
Term::RecordProj(_, head, _) => head.is_atomic(),
434+
_ => false,
435+
}
436+
}
282437
}
283438

284439
macro_rules! def_prims {
@@ -599,6 +754,21 @@ pub enum Const {
599754
Ref(usize),
600755
}
601756

757+
impl Const {
758+
/// Return the number of inhabitants of `self`.
759+
/// `None` represents infinity
760+
pub fn num_inhabitants(&self) -> Option<u128> {
761+
match self {
762+
Const::Bool(_) => Some(2),
763+
Const::U8(_, _) | Const::S8(_) => Some(1 << 8),
764+
Const::U16(_, _) | Const::S16(_) => Some(1 << 16),
765+
Const::U32(_, _) | Const::S32(_) => Some(1 << 32),
766+
Const::U64(_, _) | Const::S64(_) => Some(1 << 64),
767+
Const::F32(_) | Const::F64(_) | Const::Pos(_) | Const::Ref(_) => None,
768+
}
769+
}
770+
}
771+
602772
impl PartialEq for Const {
603773
fn eq(&self, other: &Const) -> bool {
604774
match (*self, *other) {

fathom/src/core/semantics.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ pub enum Value<'arena> {
5353
}
5454

5555
impl<'arena> Value<'arena> {
56+
pub const ERROR: Self = Self::Stuck(Head::Prim(Prim::ReportedError), Vec::new());
57+
5658
pub fn prim(prim: Prim, params: impl IntoIterator<Item = ArcValue<'arena>>) -> Value<'arena> {
5759
let params = params
5860
.into_iter()
@@ -76,6 +78,13 @@ impl<'arena> Value<'arena> {
7678
}
7779
}
7880

81+
pub fn match_record_type(&self) -> Option<&Telescope<'arena>> {
82+
match self {
83+
Value::RecordType(_, telescope) => Some(telescope),
84+
_ => None,
85+
}
86+
}
87+
7988
pub fn is_error(&self) -> bool {
8089
matches!(self, Value::Stuck(Head::Prim(Prim::ReportedError), _))
8190
}

fathom/src/env.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
//! [`SharedEnv`] to increase the amount of sharing at the expense of locality.
1919
2020
use std::fmt;
21+
use std::ops::Add;
2122

2223
/// Underlying variable representation.
2324
type RawVar = u16;
@@ -56,6 +57,13 @@ impl Index {
5657
}
5758
}
5859

60+
impl Add<EnvLen> for Index {
61+
type Output = Self;
62+
fn add(self, rhs: EnvLen) -> Self::Output {
63+
Self(self.0 + rhs.0) // FIXME: check overflow?
64+
}
65+
}
66+
5967
impl fmt::Debug for Index {
6068
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6169
write!(f, "Index(")?;
@@ -126,6 +134,13 @@ pub fn levels() -> impl Iterator<Item = Level> {
126134
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
127135
pub struct EnvLen(RawVar);
128136

137+
impl Add<Index> for EnvLen {
138+
type Output = Self;
139+
fn add(self, rhs: Index) -> Self::Output {
140+
Self(self.0 + rhs.0) // FIXME: check overflow?
141+
}
142+
}
143+
129144
impl EnvLen {
130145
/// Construct a new, empty environment.
131146
pub fn new() -> EnvLen {
@@ -152,6 +167,10 @@ impl EnvLen {
152167
Level(self.0)
153168
}
154169

170+
pub fn next(&self) -> EnvLen {
171+
Self(self.0 + 1) // FIXME: check overflow?
172+
}
173+
155174
/// Push an entry onto the environment.
156175
pub fn push(&mut self) {
157176
self.0 += 1; // FIXME: check overflow?

fathom/src/surface.rs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ pub struct ItemDef<'arena, Range> {
7676

7777
/// Surface patterns.
7878
#[derive(Debug, Clone)]
79-
pub enum Pattern<Range> {
79+
pub enum Pattern<'arena, Range> {
8080
/// Named patterns, eg. `x`, `true`, `false`
8181
Name(Range, StringId),
8282
/// Placeholder patterns, eg. `_`
@@ -93,8 +93,10 @@ pub enum Pattern<Range> {
9393
NumberLiteral(Range, StringId),
9494
/// Boolean literal patterns
9595
BooleanLiteral(Range, bool),
96-
// TODO: Record literal patterns
97-
// RecordLiteral(Range, &'arena [((Range, StringId), Pattern<'arena, Range>)]),
96+
/// Record literal patterns
97+
RecordLiteral(Range, &'arena [PatternField<'arena, Range>]),
98+
/// Tuple literal patterns
99+
TupleLiteral(Range, &'arena [Pattern<'arena, Range>]),
98100
}
99101

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

170-
impl<Range: Clone> Pattern<Range> {
172+
impl<'arena, Range: Clone> Pattern<'arena, Range> {
171173
pub fn range(&self) -> Range {
172174
match self {
173175
Pattern::Name(range, _)
174176
| Pattern::Placeholder(range)
175177
| Pattern::StringLiteral(range, _)
176178
| Pattern::NumberLiteral(range, _)
177-
| Pattern::BooleanLiteral(range, _) => range.clone(),
179+
| Pattern::BooleanLiteral(range, _)
180+
| Pattern::RecordLiteral(range, _)
181+
| Pattern::TupleLiteral(range, _) => range.clone(),
178182
}
179183
}
180184
}
@@ -199,7 +203,7 @@ pub enum Term<'arena, Range> {
199203
/// Let expressions.
200204
Let(
201205
Range,
202-
Pattern<Range>,
206+
&'arena Pattern<'arena, Range>,
203207
Option<&'arena Term<'arena, Range>>,
204208
&'arena Term<'arena, Range>,
205209
&'arena Term<'arena, Range>,
@@ -215,7 +219,7 @@ pub enum Term<'arena, Range> {
215219
Match(
216220
Range,
217221
&'arena Term<'arena, Range>,
218-
&'arena [(Pattern<Range>, Term<'arena, Range>)],
222+
&'arena [(Pattern<'arena, Range>, Term<'arena, Range>)],
219223
),
220224
/// The type of types.
221225
Universe(Range),
@@ -352,7 +356,7 @@ impl<'arena> Term<'arena, FileRange> {
352356
#[derive(Debug, Clone)]
353357
pub struct Param<'arena, Range> {
354358
pub plicity: Plicity,
355-
pub pattern: Pattern<Range>,
359+
pub pattern: Pattern<'arena, Range>,
356360
pub r#type: Option<Term<'arena, Range>>,
357361
}
358362

@@ -404,6 +408,15 @@ pub struct ExprField<'arena, Range> {
404408
expr: Option<Term<'arena, Range>>,
405409
}
406410

411+
/// A field definition in a record pattern
412+
#[derive(Debug, Clone)]
413+
pub struct PatternField<'arena, Range> {
414+
/// Label identifying the field
415+
label: (Range, StringId),
416+
/// The pattern that this field will match
417+
pattern: Pattern<'arena, Range>,
418+
}
419+
407420
/// Messages produced during parsing
408421
#[derive(Clone, Debug)]
409422
pub enum ParseMessage {
@@ -530,14 +543,14 @@ mod tests {
530543
#[test]
531544
#[cfg(target_pointer_width = "64")]
532545
fn term_size() {
533-
assert_eq!(std::mem::size_of::<Term<()>>(), 32);
546+
assert_eq!(std::mem::size_of::<Term<()>>(), 40);
534547
assert_eq!(std::mem::size_of::<Term<ByteRange>>(), 48);
535548
}
536549

537550
#[test]
538551
#[cfg(target_pointer_width = "64")]
539552
fn pattern_size() {
540-
assert_eq!(std::mem::size_of::<Pattern<()>>(), 8);
541-
assert_eq!(std::mem::size_of::<Pattern<ByteRange>>(), 16);
553+
assert_eq!(std::mem::size_of::<Pattern<()>>(), 24);
554+
assert_eq!(std::mem::size_of::<Pattern<ByteRange>>(), 32);
542555
}
543556
}

0 commit comments

Comments
 (0)