mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-01 07:41:12 +00:00
Delete obsolete stuff
This commit is contained in:
parent
0fd2bde5cd
commit
864eecf44c
30 changed files with 26 additions and 3805 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -1,3 +1,5 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ansi_term"
|
name = "ansi_term"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
pub enum Annotation {}
|
|
|
@ -1,24 +0,0 @@
|
||||||
use typ::Type;
|
|
||||||
|
|
||||||
// constrainDecls :: Can.Decls -> Constraint -> IO Constraint
|
|
||||||
// constrainDecls decls finalConstraint =
|
|
||||||
// case decls of
|
|
||||||
// Can.Declare def otherDecls ->
|
|
||||||
// Expr.constrainDef Map.empty def =<< constrainDecls otherDecls finalConstraint
|
|
||||||
|
|
||||||
// Can.DeclareRec defs otherDecls ->
|
|
||||||
// Expr.constrainRecursiveDefs Map.empty defs =<< constrainDecls otherDecls finalConstraint
|
|
||||||
|
|
||||||
// Can.SaveTheEnvironment ->
|
|
||||||
// return finalConstraint
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub type ExpectedType = Type;
|
|
||||||
|
|
||||||
|
|
||||||
pub enum Constraint {
|
|
||||||
True,
|
|
||||||
Equal(Type, ExpectedType),
|
|
||||||
Batch(Vec<Constraint>),
|
|
||||||
}
|
|
|
@ -1,301 +0,0 @@
|
||||||
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
|
|
||||||
// file at the top-level directory of this distribution and at
|
|
||||||
// http://rust-lang.org/COPYRIGHT.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
||||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
||||||
// option. This file may not be copied, modified, or distributed
|
|
||||||
// except according to those terms.
|
|
||||||
|
|
||||||
/// A very simple BitVector type.
|
|
||||||
pub struct BitVector {
|
|
||||||
data: Vec<u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BitVector {
|
|
||||||
pub fn new(num_bits: usize) -> BitVector {
|
|
||||||
let num_words = u64s(num_bits);
|
|
||||||
BitVector { data: vec![0; num_words] }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn contains(&self, bit: usize) -> bool {
|
|
||||||
let (word, mask) = word_mask(bit);
|
|
||||||
(self.data[word] & mask) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true if the bit has changed.
|
|
||||||
pub fn insert(&mut self, bit: usize) -> bool {
|
|
||||||
let (word, mask) = word_mask(bit);
|
|
||||||
let data = &mut self.data[word];
|
|
||||||
let value = *data;
|
|
||||||
let new_value = value | mask;
|
|
||||||
*data = new_value;
|
|
||||||
new_value != value
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert_all(&mut self, all: &BitVector) -> bool {
|
|
||||||
assert!(self.data.len() == all.data.len());
|
|
||||||
let mut changed = false;
|
|
||||||
for (i, j) in self.data.iter_mut().zip(&all.data) {
|
|
||||||
let value = *i;
|
|
||||||
*i = value | *j;
|
|
||||||
if value != *i {
|
|
||||||
changed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
changed
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn grow(&mut self, num_bits: usize) {
|
|
||||||
let num_words = u64s(num_bits);
|
|
||||||
let extra_words = self.data.len() - num_words;
|
|
||||||
self.data.extend((0..extra_words).map(|_| 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Iterates over indexes of set bits in a sorted order
|
|
||||||
pub fn iter<'a>(&'a self) -> BitVectorIter<'a> {
|
|
||||||
BitVectorIter {
|
|
||||||
iter: self.data.iter(),
|
|
||||||
current: 0,
|
|
||||||
idx: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct BitVectorIter<'a> {
|
|
||||||
iter: ::std::slice::Iter<'a, u64>,
|
|
||||||
current: u64,
|
|
||||||
idx: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Iterator for BitVectorIter<'a> {
|
|
||||||
type Item = usize;
|
|
||||||
fn next(&mut self) -> Option<usize> {
|
|
||||||
while self.current == 0 {
|
|
||||||
self.current = if let Some(&i) = self.iter.next() {
|
|
||||||
if i == 0 {
|
|
||||||
self.idx += 64;
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
self.idx = u64s(self.idx) * 64;
|
|
||||||
i
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let offset = self.current.trailing_zeros() as usize;
|
|
||||||
self.current >>= offset;
|
|
||||||
self.current >>= 1; // shift otherwise overflows for 0b1000_0000_…_0000
|
|
||||||
self.idx += offset + 1;
|
|
||||||
return Some(self.idx - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A "bit matrix" is basically a square matrix of booleans
|
|
||||||
/// represented as one gigantic bitvector. In other words, it is as if
|
|
||||||
/// you have N bitvectors, each of length N. Note that `elements` here is `N`/
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct BitMatrix {
|
|
||||||
elements: usize,
|
|
||||||
vector: Vec<u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BitMatrix {
|
|
||||||
// Create a new `elements x elements` matrix, initially empty.
|
|
||||||
pub fn new(elements: usize) -> BitMatrix {
|
|
||||||
// For every element, we need one bit for every other
|
|
||||||
// element. Round up to an even number of u64s.
|
|
||||||
let u64s_per_elem = u64s(elements);
|
|
||||||
BitMatrix {
|
|
||||||
elements: elements,
|
|
||||||
vector: vec![0; elements * u64s_per_elem],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The range of bits for a given element.
|
|
||||||
fn range(&self, element: usize) -> (usize, usize) {
|
|
||||||
let u64s_per_elem = u64s(self.elements);
|
|
||||||
let start = element * u64s_per_elem;
|
|
||||||
(start, start + u64s_per_elem)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add(&mut self, source: usize, target: usize) -> bool {
|
|
||||||
let (start, _) = self.range(source);
|
|
||||||
let (word, mask) = word_mask(target);
|
|
||||||
let mut vector = &mut self.vector[..];
|
|
||||||
let v1 = vector[start + word];
|
|
||||||
let v2 = v1 | mask;
|
|
||||||
vector[start + word] = v2;
|
|
||||||
v1 != v2
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Do the bits from `source` contain `target`?
|
|
||||||
///
|
|
||||||
/// Put another way, if the matrix represents (transitive)
|
|
||||||
/// reachability, can `source` reach `target`?
|
|
||||||
pub fn contains(&self, source: usize, target: usize) -> bool {
|
|
||||||
let (start, _) = self.range(source);
|
|
||||||
let (word, mask) = word_mask(target);
|
|
||||||
(self.vector[start + word] & mask) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns those indices that are reachable from both `a` and
|
|
||||||
/// `b`. This is an O(n) operation where `n` is the number of
|
|
||||||
/// elements (somewhat independent from the actual size of the
|
|
||||||
/// intersection, in particular).
|
|
||||||
pub fn intersection(&self, a: usize, b: usize) -> Vec<usize> {
|
|
||||||
let (a_start, a_end) = self.range(a);
|
|
||||||
let (b_start, b_end) = self.range(b);
|
|
||||||
let mut result = Vec::with_capacity(self.elements);
|
|
||||||
for (base, (i, j)) in (a_start..a_end).zip(b_start..b_end).enumerate() {
|
|
||||||
let mut v = self.vector[i] & self.vector[j];
|
|
||||||
for bit in 0..64 {
|
|
||||||
if v == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if v & 0x1 != 0 {
|
|
||||||
result.push(base * 64 + bit);
|
|
||||||
}
|
|
||||||
v >>= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add the bits from `read` to the bits from `write`,
|
|
||||||
/// return true if anything changed.
|
|
||||||
///
|
|
||||||
/// This is used when computing transitive reachability because if
|
|
||||||
/// you have an edge `write -> read`, because in that case
|
|
||||||
/// `write` can reach everything that `read` can (and
|
|
||||||
/// potentially more).
|
|
||||||
pub fn merge(&mut self, read: usize, write: usize) -> bool {
|
|
||||||
let (read_start, read_end) = self.range(read);
|
|
||||||
let (write_start, write_end) = self.range(write);
|
|
||||||
let vector = &mut self.vector[..];
|
|
||||||
let mut changed = false;
|
|
||||||
for (read_index, write_index) in (read_start..read_end).zip(write_start..write_end) {
|
|
||||||
let v1 = vector[write_index];
|
|
||||||
let v2 = v1 | vector[read_index];
|
|
||||||
vector[write_index] = v2;
|
|
||||||
changed = changed | (v1 != v2);
|
|
||||||
}
|
|
||||||
changed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn u64s(elements: usize) -> usize {
|
|
||||||
(elements + 63) / 64
|
|
||||||
}
|
|
||||||
|
|
||||||
fn word_mask(index: usize) -> (usize, u64) {
|
|
||||||
let word = index / 64;
|
|
||||||
let mask = 1 << (index % 64);
|
|
||||||
(word, mask)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn bitvec_iter_works() {
|
|
||||||
let mut bitvec = BitVector::new(100);
|
|
||||||
bitvec.insert(1);
|
|
||||||
bitvec.insert(10);
|
|
||||||
bitvec.insert(19);
|
|
||||||
bitvec.insert(62);
|
|
||||||
bitvec.insert(63);
|
|
||||||
bitvec.insert(64);
|
|
||||||
bitvec.insert(65);
|
|
||||||
bitvec.insert(66);
|
|
||||||
bitvec.insert(99);
|
|
||||||
assert_eq!(bitvec.iter().collect::<Vec<_>>(),
|
|
||||||
[1, 10, 19, 62, 63, 64, 65, 66, 99]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn bitvec_iter_works_2() {
|
|
||||||
let mut bitvec = BitVector::new(300);
|
|
||||||
bitvec.insert(1);
|
|
||||||
bitvec.insert(10);
|
|
||||||
bitvec.insert(19);
|
|
||||||
bitvec.insert(62);
|
|
||||||
bitvec.insert(66);
|
|
||||||
bitvec.insert(99);
|
|
||||||
bitvec.insert(299);
|
|
||||||
assert_eq!(bitvec.iter().collect::<Vec<_>>(),
|
|
||||||
[1, 10, 19, 62, 66, 99, 299]);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn bitvec_iter_works_3() {
|
|
||||||
let mut bitvec = BitVector::new(319);
|
|
||||||
bitvec.insert(0);
|
|
||||||
bitvec.insert(127);
|
|
||||||
bitvec.insert(191);
|
|
||||||
bitvec.insert(255);
|
|
||||||
bitvec.insert(319);
|
|
||||||
assert_eq!(bitvec.iter().collect::<Vec<_>>(), [0, 127, 191, 255, 319]);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn union_two_vecs() {
|
|
||||||
let mut vec1 = BitVector::new(65);
|
|
||||||
let mut vec2 = BitVector::new(65);
|
|
||||||
assert!(vec1.insert(3));
|
|
||||||
assert!(!vec1.insert(3));
|
|
||||||
assert!(vec2.insert(5));
|
|
||||||
assert!(vec2.insert(64));
|
|
||||||
assert!(vec1.insert_all(&vec2));
|
|
||||||
assert!(!vec1.insert_all(&vec2));
|
|
||||||
assert!(vec1.contains(3));
|
|
||||||
assert!(!vec1.contains(4));
|
|
||||||
assert!(vec1.contains(5));
|
|
||||||
assert!(!vec1.contains(63));
|
|
||||||
assert!(vec1.contains(64));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn grow() {
|
|
||||||
let mut vec1 = BitVector::new(65);
|
|
||||||
assert!(vec1.insert(3));
|
|
||||||
assert!(!vec1.insert(3));
|
|
||||||
assert!(vec1.insert(5));
|
|
||||||
assert!(vec1.insert(64));
|
|
||||||
vec1.grow(128);
|
|
||||||
assert!(vec1.contains(3));
|
|
||||||
assert!(vec1.contains(5));
|
|
||||||
assert!(vec1.contains(64));
|
|
||||||
assert!(!vec1.contains(126));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn matrix_intersection() {
|
|
||||||
let mut vec1 = BitMatrix::new(200);
|
|
||||||
|
|
||||||
// (*) Elements reachable from both 2 and 65.
|
|
||||||
|
|
||||||
vec1.add(2, 3);
|
|
||||||
vec1.add(2, 6);
|
|
||||||
vec1.add(2, 10); // (*)
|
|
||||||
vec1.add(2, 64); // (*)
|
|
||||||
vec1.add(2, 65);
|
|
||||||
vec1.add(2, 130);
|
|
||||||
vec1.add(2, 160); // (*)
|
|
||||||
|
|
||||||
vec1.add(64, 133);
|
|
||||||
|
|
||||||
vec1.add(65, 2);
|
|
||||||
vec1.add(65, 8);
|
|
||||||
vec1.add(65, 10); // (*)
|
|
||||||
vec1.add(65, 64); // (*)
|
|
||||||
vec1.add(65, 68);
|
|
||||||
vec1.add(65, 133);
|
|
||||||
vec1.add(65, 160); // (*)
|
|
||||||
|
|
||||||
let intersection = vec1.intersection(2, 64);
|
|
||||||
assert!(intersection.is_empty());
|
|
||||||
|
|
||||||
let intersection = vec1.intersection(2, 65);
|
|
||||||
assert_eq!(intersection, &[10, 64, 160]);
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
|
|
||||||
// file at the top-level directory of this distribution and at
|
|
||||||
// http://rust-lang.org/COPYRIGHT.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
||||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
||||||
// option. This file may not be copied, modified, or distributed
|
|
||||||
// except according to those terms.
|
|
||||||
|
|
||||||
//! An implementation of union-find. See the `unify` module for more
|
|
||||||
//! details.
|
|
||||||
|
|
||||||
pub mod snapshot_vec;
|
|
||||||
pub mod unify;
|
|
|
@ -1,370 +0,0 @@
|
||||||
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
|
|
||||||
// file at the top-level directory of this distribution and at
|
|
||||||
// http://rust-lang.org/COPYRIGHT.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
||||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
||||||
// option. This file may not be copied, modified, or distributed
|
|
||||||
// except according to those terms.
|
|
||||||
|
|
||||||
//! A utility class for implementing "snapshottable" things; a snapshottable data structure permits
|
|
||||||
//! you to take a snapshot (via `start_snapshot`) and then, after making some changes, elect either
|
|
||||||
//! to rollback to the start of the snapshot or commit those changes.
|
|
||||||
//!
|
|
||||||
//! This vector is intended to be used as part of an abstraction, not serve as a complete
|
|
||||||
//! abstraction on its own. As such, while it will roll back most changes on its own, it also
|
|
||||||
//! supports a `get_mut` operation that gives you an arbitrary mutable pointer into the vector. To
|
|
||||||
//! ensure that any changes you make this with this pointer are rolled back, you must invoke
|
|
||||||
//! `record` to record any changes you make and also supplying a delegate capable of reversing
|
|
||||||
//! those changes.
|
|
||||||
|
|
||||||
use self::UndoLog::*;
|
|
||||||
|
|
||||||
use std::fmt;
|
|
||||||
use std::mem;
|
|
||||||
use std::ops;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum UndoLog<D: SnapshotVecDelegate> {
|
|
||||||
/// New variable with given index was created.
|
|
||||||
NewElem(usize),
|
|
||||||
|
|
||||||
/// Variable with given index was changed *from* the given value.
|
|
||||||
SetElem(usize, D::Value),
|
|
||||||
|
|
||||||
/// Extensible set of actions
|
|
||||||
Other(D::Undo),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SnapshotVec<D: SnapshotVecDelegate> {
|
|
||||||
values: Vec<D::Value>,
|
|
||||||
undo_log: Vec<UndoLog<D>>,
|
|
||||||
num_open_snapshots: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D> fmt::Debug for SnapshotVec<D>
|
|
||||||
where D: SnapshotVecDelegate,
|
|
||||||
D: fmt::Debug,
|
|
||||||
D::Undo: fmt::Debug,
|
|
||||||
D::Value: fmt::Debug
|
|
||||||
{
|
|
||||||
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
fmt.debug_struct("SnapshotVec")
|
|
||||||
.field("values", &self.values)
|
|
||||||
.field("undo_log", &self.undo_log)
|
|
||||||
.field("num_open_snapshots", &self.num_open_snapshots)
|
|
||||||
.finish()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Snapshots are tokens that should be created/consumed linearly.
|
|
||||||
pub struct Snapshot {
|
|
||||||
// Length of the undo log at the time the snapshot was taken.
|
|
||||||
length: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait SnapshotVecDelegate {
|
|
||||||
type Value;
|
|
||||||
type Undo;
|
|
||||||
|
|
||||||
fn reverse(values: &mut Vec<Self::Value>, action: Self::Undo);
|
|
||||||
}
|
|
||||||
|
|
||||||
// HACK(eddyb) manual impl avoids `Default` bound on `D`.
|
|
||||||
impl<D: SnapshotVecDelegate> Default for SnapshotVec<D> {
|
|
||||||
fn default() -> Self {
|
|
||||||
SnapshotVec {
|
|
||||||
values: Vec::new(),
|
|
||||||
undo_log: Vec::new(),
|
|
||||||
num_open_snapshots: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D: SnapshotVecDelegate> SnapshotVec<D> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_capacity(c: usize) -> SnapshotVec<D> {
|
|
||||||
SnapshotVec {
|
|
||||||
values: Vec::with_capacity(c),
|
|
||||||
undo_log: Vec::new(),
|
|
||||||
num_open_snapshots: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn in_snapshot(&self) -> bool {
|
|
||||||
self.num_open_snapshots > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn record(&mut self, action: D::Undo) {
|
|
||||||
if self.in_snapshot() {
|
|
||||||
self.undo_log.push(Other(action));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.values.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push(&mut self, elem: D::Value) -> usize {
|
|
||||||
let len = self.values.len();
|
|
||||||
self.values.push(elem);
|
|
||||||
|
|
||||||
if self.in_snapshot() {
|
|
||||||
self.undo_log.push(NewElem(len));
|
|
||||||
}
|
|
||||||
|
|
||||||
len
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self, index: usize) -> &D::Value {
|
|
||||||
&self.values[index]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reserve space for new values, just like an ordinary vec.
|
|
||||||
pub fn reserve(&mut self, additional: usize) {
|
|
||||||
// This is not affected by snapshots or anything.
|
|
||||||
self.values.reserve(additional);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a mutable pointer into the vec; whatever changes you make here cannot be undone
|
|
||||||
/// automatically, so you should be sure call `record()` with some sort of suitable undo
|
|
||||||
/// action.
|
|
||||||
pub fn get_mut(&mut self, index: usize) -> &mut D::Value {
|
|
||||||
&mut self.values[index]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the element at the given index. The old value will saved (and perhaps restored) if
|
|
||||||
/// a snapshot is active.
|
|
||||||
pub fn set(&mut self, index: usize, new_elem: D::Value) {
|
|
||||||
let old_elem = mem::replace(&mut self.values[index], new_elem);
|
|
||||||
if self.in_snapshot() {
|
|
||||||
self.undo_log.push(SetElem(index, old_elem));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates all elements. Potentially more efficient -- but
|
|
||||||
/// otherwise equivalent to -- invoking `set` for each element.
|
|
||||||
pub fn set_all(&mut self, mut new_elems: impl FnMut(usize) -> D::Value) {
|
|
||||||
if !self.in_snapshot() {
|
|
||||||
for (slot, index) in self.values.iter_mut().zip(0..) {
|
|
||||||
*slot = new_elems(index);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for i in 0..self.values.len() {
|
|
||||||
self.set(i, new_elems(i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update<OP>(&mut self, index: usize, op: OP)
|
|
||||||
where
|
|
||||||
OP: FnOnce(&mut D::Value),
|
|
||||||
D::Value: Clone,
|
|
||||||
{
|
|
||||||
if self.in_snapshot() {
|
|
||||||
let old_elem = self.values[index].clone();
|
|
||||||
self.undo_log.push(SetElem(index, old_elem));
|
|
||||||
}
|
|
||||||
op(&mut self.values[index]);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start_snapshot(&mut self) -> Snapshot {
|
|
||||||
let length = self.undo_log.len();
|
|
||||||
self.num_open_snapshots += 1;
|
|
||||||
Snapshot { length: length }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn actions_since_snapshot(&self, snapshot: &Snapshot) -> &[UndoLog<D>] {
|
|
||||||
&self.undo_log[snapshot.length..]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_open_snapshot(&self, snapshot: &Snapshot) {
|
|
||||||
// Failures here may indicate a failure to follow a stack discipline.
|
|
||||||
assert!(self.undo_log.len() >= snapshot.length);
|
|
||||||
assert!(self.num_open_snapshots > 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rollback_to(&mut self, snapshot: Snapshot) {
|
|
||||||
debug!("rollback_to({})", snapshot.length);
|
|
||||||
|
|
||||||
self.assert_open_snapshot(&snapshot);
|
|
||||||
|
|
||||||
while self.undo_log.len() > snapshot.length {
|
|
||||||
match self.undo_log.pop().unwrap() {
|
|
||||||
NewElem(i) => {
|
|
||||||
self.values.pop();
|
|
||||||
assert!(self.values.len() == i);
|
|
||||||
}
|
|
||||||
|
|
||||||
SetElem(i, v) => {
|
|
||||||
self.values[i] = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
Other(u) => {
|
|
||||||
D::reverse(&mut self.values, u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
self.num_open_snapshots -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Commits all changes since the last snapshot. Of course, they
|
|
||||||
/// can still be undone if there is a snapshot further out.
|
|
||||||
pub fn commit(&mut self, snapshot: Snapshot) {
|
|
||||||
debug!("commit({})", snapshot.length);
|
|
||||||
|
|
||||||
self.assert_open_snapshot(&snapshot);
|
|
||||||
|
|
||||||
if self.num_open_snapshots == 1 {
|
|
||||||
// The root snapshot. It's safe to clear the undo log because
|
|
||||||
// there's no snapshot further out that we might need to roll back
|
|
||||||
// to.
|
|
||||||
assert!(snapshot.length == 0);
|
|
||||||
self.undo_log.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.num_open_snapshots -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D: SnapshotVecDelegate> ops::Deref for SnapshotVec<D> {
|
|
||||||
type Target = [D::Value];
|
|
||||||
fn deref(&self) -> &[D::Value] {
|
|
||||||
&*self.values
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D: SnapshotVecDelegate> ops::DerefMut for SnapshotVec<D> {
|
|
||||||
fn deref_mut(&mut self) -> &mut [D::Value] {
|
|
||||||
&mut *self.values
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D: SnapshotVecDelegate> ops::Index<usize> for SnapshotVec<D> {
|
|
||||||
type Output = D::Value;
|
|
||||||
fn index(&self, index: usize) -> &D::Value {
|
|
||||||
self.get(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D: SnapshotVecDelegate> ops::IndexMut<usize> for SnapshotVec<D> {
|
|
||||||
fn index_mut(&mut self, index: usize) -> &mut D::Value {
|
|
||||||
self.get_mut(index)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D: SnapshotVecDelegate> Extend<D::Value> for SnapshotVec<D> {
|
|
||||||
fn extend<T>(&mut self, iterable: T)
|
|
||||||
where
|
|
||||||
T: IntoIterator<Item = D::Value>,
|
|
||||||
{
|
|
||||||
let initial_len = self.values.len();
|
|
||||||
self.values.extend(iterable);
|
|
||||||
let final_len = self.values.len();
|
|
||||||
|
|
||||||
if self.in_snapshot() {
|
|
||||||
self.undo_log.extend((initial_len..final_len).map(|len| NewElem(len)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D: SnapshotVecDelegate> Clone for SnapshotVec<D>
|
|
||||||
where
|
|
||||||
D::Value: Clone,
|
|
||||||
D::Undo: Clone,
|
|
||||||
{
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
SnapshotVec {
|
|
||||||
values: self.values.clone(),
|
|
||||||
undo_log: self.undo_log.clone(),
|
|
||||||
num_open_snapshots: self.num_open_snapshots,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<D: SnapshotVecDelegate> Clone for UndoLog<D>
|
|
||||||
where
|
|
||||||
D::Value: Clone,
|
|
||||||
D::Undo: Clone,
|
|
||||||
{
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
match *self {
|
|
||||||
NewElem(i) => NewElem(i),
|
|
||||||
SetElem(i, ref v) => SetElem(i, v.clone()),
|
|
||||||
Other(ref u) => Other(u.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SnapshotVecDelegate for i32 {
|
|
||||||
type Value = i32;
|
|
||||||
type Undo = ();
|
|
||||||
|
|
||||||
fn reverse(_: &mut Vec<i32>, _: ()) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn basic() {
|
|
||||||
let mut vec: SnapshotVec<i32> = SnapshotVec::default();
|
|
||||||
assert!(!vec.in_snapshot());
|
|
||||||
assert_eq!(vec.len(), 0);
|
|
||||||
vec.push(22);
|
|
||||||
vec.push(33);
|
|
||||||
assert_eq!(vec.len(), 2);
|
|
||||||
assert_eq!(*vec.get(0), 22);
|
|
||||||
assert_eq!(*vec.get(1), 33);
|
|
||||||
vec.set(1, 34);
|
|
||||||
assert_eq!(vec.len(), 2);
|
|
||||||
assert_eq!(*vec.get(0), 22);
|
|
||||||
assert_eq!(*vec.get(1), 34);
|
|
||||||
|
|
||||||
let snapshot = vec.start_snapshot();
|
|
||||||
assert!(vec.in_snapshot());
|
|
||||||
|
|
||||||
vec.push(44);
|
|
||||||
vec.push(55);
|
|
||||||
vec.set(1, 35);
|
|
||||||
assert_eq!(vec.len(), 4);
|
|
||||||
assert_eq!(*vec.get(0), 22);
|
|
||||||
assert_eq!(*vec.get(1), 35);
|
|
||||||
assert_eq!(*vec.get(2), 44);
|
|
||||||
assert_eq!(*vec.get(3), 55);
|
|
||||||
|
|
||||||
vec.rollback_to(snapshot);
|
|
||||||
assert!(!vec.in_snapshot());
|
|
||||||
|
|
||||||
assert_eq!(vec.len(), 2);
|
|
||||||
assert_eq!(*vec.get(0), 22);
|
|
||||||
assert_eq!(*vec.get(1), 34);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[should_panic]
|
|
||||||
fn out_of_order() {
|
|
||||||
let mut vec: SnapshotVec<i32> = SnapshotVec::default();
|
|
||||||
vec.push(22);
|
|
||||||
let snapshot1 = vec.start_snapshot();
|
|
||||||
vec.push(33);
|
|
||||||
let snapshot2 = vec.start_snapshot();
|
|
||||||
vec.push(44);
|
|
||||||
vec.rollback_to(snapshot1); // bogus, but accepted
|
|
||||||
vec.rollback_to(snapshot2); // asserts
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn nested_commit_then_rollback() {
|
|
||||||
let mut vec: SnapshotVec<i32> = SnapshotVec::default();
|
|
||||||
vec.push(22);
|
|
||||||
let snapshot1 = vec.start_snapshot();
|
|
||||||
let snapshot2 = vec.start_snapshot();
|
|
||||||
vec.set(0, 23);
|
|
||||||
vec.commit(snapshot2);
|
|
||||||
assert_eq!(*vec.get(0), 23);
|
|
||||||
vec.rollback_to(snapshot1);
|
|
||||||
assert_eq!(*vec.get(0), 22);
|
|
||||||
}
|
|
|
@ -1,214 +0,0 @@
|
||||||
// This is a fork of ena, whose copyright and license info is in ena/unify/mod.rs
|
|
||||||
|
|
||||||
#[cfg(feature = "persistent")]
|
|
||||||
use dogged::DVec;
|
|
||||||
use ena::snapshot_vec as sv;
|
|
||||||
use std::ops;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
|
|
||||||
use super::{VarValue, UnifyKey};
|
|
||||||
|
|
||||||
#[allow(dead_code)] // rustc BUG
|
|
||||||
#[allow(type_alias_bounds)]
|
|
||||||
type Key<S: UnificationStore> = <S as UnificationStore>::Key;
|
|
||||||
|
|
||||||
/// Largely internal trait implemented by the unification table
|
|
||||||
/// backing store types. The most common such type is `InPlace`,
|
|
||||||
/// which indicates a standard, mutable unification table.
|
|
||||||
pub trait UnificationStore:
|
|
||||||
ops::Index<usize, Output = VarValue<Key<Self>>> + Clone + Default
|
|
||||||
{
|
|
||||||
type Key: UnifyKey<Value = Self::Value>;
|
|
||||||
type Value: Debug + Clone;
|
|
||||||
type Snapshot;
|
|
||||||
|
|
||||||
fn start_snapshot(&mut self) -> Self::Snapshot;
|
|
||||||
|
|
||||||
fn rollback_to(&mut self, snapshot: Self::Snapshot);
|
|
||||||
|
|
||||||
fn commit(&mut self, snapshot: Self::Snapshot);
|
|
||||||
|
|
||||||
fn reset_unifications(
|
|
||||||
&mut self,
|
|
||||||
value: impl FnMut(u32) -> VarValue<Self::Key>,
|
|
||||||
);
|
|
||||||
|
|
||||||
fn len(&self) -> usize;
|
|
||||||
|
|
||||||
fn push(&mut self, value: VarValue<Self::Key>);
|
|
||||||
|
|
||||||
fn reserve(&mut self, num_new_values: usize);
|
|
||||||
|
|
||||||
fn update<F>(&mut self, index: usize, op: F)
|
|
||||||
where F: FnOnce(&mut VarValue<Self::Key>);
|
|
||||||
|
|
||||||
fn tag() -> &'static str {
|
|
||||||
Self::Key::tag()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Backing store for an in-place unification table.
|
|
||||||
/// Not typically used directly.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct InPlace<K: UnifyKey> {
|
|
||||||
values: sv::SnapshotVec<Delegate<K>>
|
|
||||||
}
|
|
||||||
|
|
||||||
// HACK(eddyb) manual impl avoids `Default` bound on `K`.
|
|
||||||
impl<K: UnifyKey> Default for InPlace<K> {
|
|
||||||
fn default() -> Self {
|
|
||||||
InPlace { values: sv::SnapshotVec::new() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: UnifyKey> UnificationStore for InPlace<K> {
|
|
||||||
type Key = K;
|
|
||||||
type Value = K::Value;
|
|
||||||
type Snapshot = sv::Snapshot;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn start_snapshot(&mut self) -> Self::Snapshot {
|
|
||||||
self.values.start_snapshot()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn rollback_to(&mut self, snapshot: Self::Snapshot) {
|
|
||||||
self.values.rollback_to(snapshot);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn commit(&mut self, snapshot: Self::Snapshot) {
|
|
||||||
self.values.commit(snapshot);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn reset_unifications(
|
|
||||||
&mut self,
|
|
||||||
mut value: impl FnMut(u32) -> VarValue<Self::Key>,
|
|
||||||
) {
|
|
||||||
self.values.set_all(|i| value(i as u32));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn len(&self) -> usize {
|
|
||||||
self.values.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn push(&mut self, value: VarValue<Self::Key>) {
|
|
||||||
self.values.push(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn reserve(&mut self, num_new_values: usize) {
|
|
||||||
self.values.reserve(num_new_values);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn update<F>(&mut self, index: usize, op: F)
|
|
||||||
where F: FnOnce(&mut VarValue<Self::Key>)
|
|
||||||
{
|
|
||||||
self.values.update(index, op)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K> ops::Index<usize> for InPlace<K>
|
|
||||||
where K: UnifyKey
|
|
||||||
{
|
|
||||||
type Output = VarValue<K>;
|
|
||||||
fn index(&self, index: usize) -> &VarValue<K> {
|
|
||||||
&self.values[index]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug)]
|
|
||||||
struct Delegate<K>(PhantomData<K>);
|
|
||||||
|
|
||||||
impl<K: UnifyKey> sv::SnapshotVecDelegate for Delegate<K> {
|
|
||||||
type Value = VarValue<K>;
|
|
||||||
type Undo = ();
|
|
||||||
|
|
||||||
fn reverse(_: &mut Vec<VarValue<K>>, _: ()) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "persistent")]
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
pub struct Persistent<K: UnifyKey> {
|
|
||||||
values: DVec<VarValue<K>>
|
|
||||||
}
|
|
||||||
|
|
||||||
// HACK(eddyb) manual impl avoids `Default` bound on `K`.
|
|
||||||
#[cfg(feature = "persistent")]
|
|
||||||
impl<K: UnifyKey> Default for Persistent<K> {
|
|
||||||
fn default() -> Self {
|
|
||||||
Persistent { values: DVec::new() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "persistent")]
|
|
||||||
impl<K: UnifyKey> UnificationStore for Persistent<K> {
|
|
||||||
type Key = K;
|
|
||||||
type Value = K::Value;
|
|
||||||
type Snapshot = Self;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn start_snapshot(&mut self) -> Self::Snapshot {
|
|
||||||
self.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn rollback_to(&mut self, snapshot: Self::Snapshot) {
|
|
||||||
*self = snapshot;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn commit(&mut self, _snapshot: Self::Snapshot) {
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn reset_unifications(
|
|
||||||
&mut self,
|
|
||||||
mut value: impl FnMut(u32) -> VarValue<Self::Key>,
|
|
||||||
) {
|
|
||||||
// Without extending dogged, there isn't obviously a more
|
|
||||||
// efficient way to do this. But it's pretty dumb. Maybe
|
|
||||||
// dogged needs a `map`.
|
|
||||||
for i in 0 .. self.values.len() {
|
|
||||||
self.values[i] = value(i as u32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn len(&self) -> usize {
|
|
||||||
self.values.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn push(&mut self, value: VarValue<Self::Key>) {
|
|
||||||
self.values.push(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn reserve(&mut self, _num_new_values: usize) {
|
|
||||||
// not obviously relevant to DVec.
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn update<F>(&mut self, index: usize, op: F)
|
|
||||||
where F: FnOnce(&mut VarValue<Self::Key>)
|
|
||||||
{
|
|
||||||
let p = &mut self.values[index];
|
|
||||||
op(p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "persistent")]
|
|
||||||
impl<K> ops::Index<usize> for Persistent<K>
|
|
||||||
where K: UnifyKey
|
|
||||||
{
|
|
||||||
type Output = VarValue<K>;
|
|
||||||
fn index(&self, index: usize) -> &VarValue<K> {
|
|
||||||
&self.values[index]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,444 +0,0 @@
|
||||||
// This is a fork of ena, whose copyright and license info is below.
|
|
||||||
//
|
|
||||||
// The fork was made primarily in order to support unifying type unions, which
|
|
||||||
// requires looking up the current values of keys in the middle of unification.
|
|
||||||
// This fork implements that by replacing the UnificationValue trait with
|
|
||||||
// FnOnce callbacks which accept the table as well as the values to unify.
|
|
||||||
|
|
||||||
// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
|
|
||||||
// file at the top-level directory of this distribution and at
|
|
||||||
// http://rust-lang.org/COPYRIGHT.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
|
||||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
|
||||||
// option. This file may not be copied, modified, or distributed
|
|
||||||
// except according to those terms.
|
|
||||||
|
|
||||||
//! Union-find implementation. The main type is `UnificationTable`.
|
|
||||||
//!
|
|
||||||
//! You can define your own type for the *keys* in the table, but you
|
|
||||||
//! must implement `UnifyKey` for that type. The assumption is that
|
|
||||||
//! keys will be newtyped integers, hence we require that they
|
|
||||||
//! implement `Copy`.
|
|
||||||
//!
|
|
||||||
//! Keys can have values associated with them. The assumption is that
|
|
||||||
//! these values are cheaply cloneable (ideally, `Copy`), and some of
|
|
||||||
//! the interfaces are oriented around that assumption. If you just
|
|
||||||
//! want the classical "union-find" algorithm where you group things
|
|
||||||
//! into sets, use the `Value` type of `()`.
|
|
||||||
//!
|
|
||||||
//! When you have keys with non-trivial values, you must also define
|
|
||||||
//! how those values can be merged.
|
|
||||||
//!
|
|
||||||
//! The best way to see how it is used is to read the `tests.rs` file;
|
|
||||||
//! search for e.g. `UnitKey`.
|
|
||||||
|
|
||||||
use std::marker;
|
|
||||||
use std::fmt::Debug;
|
|
||||||
|
|
||||||
mod backing_vec;
|
|
||||||
pub use self::backing_vec::{InPlace, UnificationStore};
|
|
||||||
|
|
||||||
#[cfg(feature = "persistent")]
|
|
||||||
pub use self::backing_vec::Persistent;
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests;
|
|
||||||
|
|
||||||
/// This trait is implemented by any type that can serve as a type
|
|
||||||
/// variable. We call such variables *unification keys*. For example,
|
|
||||||
/// this trait is implemented by `IntVid`, which represents integral
|
|
||||||
/// variables.
|
|
||||||
///
|
|
||||||
/// Each key type has an associated value type `V`. For example, for
|
|
||||||
/// `IntVid`, this is `Option<IntVarValue>`, representing some
|
|
||||||
/// (possibly not yet known) sort of integer.
|
|
||||||
///
|
|
||||||
/// Clients are expected to provide implementations of this trait; you
|
|
||||||
/// can see some examples in the `test` module.
|
|
||||||
pub trait UnifyKey: Copy + Clone + Debug + PartialEq {
|
|
||||||
type Value: Clone + Debug;
|
|
||||||
|
|
||||||
fn index(&self) -> u32;
|
|
||||||
|
|
||||||
fn from_index(u: u32) -> Self;
|
|
||||||
|
|
||||||
fn tag() -> &'static str;
|
|
||||||
|
|
||||||
/// If true, then `self` should be preferred as root to `other`.
|
|
||||||
/// Note that we assume a consistent partial ordering, so
|
|
||||||
/// returning true implies that `other.prefer_as_root_to(self)`
|
|
||||||
/// would return false. If there is no ordering between two keys
|
|
||||||
/// (i.e., `a.prefer_as_root_to(b)` and `b.prefer_as_root_to(a)`
|
|
||||||
/// both return false) then the rank will be used to determine the
|
|
||||||
/// root in an optimal way.
|
|
||||||
///
|
|
||||||
/// NB. The only reason to implement this method is if you want to
|
|
||||||
/// control what value is returned from `find()`. In general, it
|
|
||||||
/// is better to let the unification table determine the root,
|
|
||||||
/// since overriding the rank can cause execution time to increase
|
|
||||||
/// dramatically.
|
|
||||||
#[allow(unused_variables)]
|
|
||||||
fn order_roots(
|
|
||||||
a: Self,
|
|
||||||
a_value: &Self::Value,
|
|
||||||
b: Self,
|
|
||||||
b_value: &Self::Value,
|
|
||||||
) -> Option<(Self, Self)> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/// Value of a unification key. We implement Tarjan's union-find
|
|
||||||
/// algorithm: when two keys are unified, one of them is converted
|
|
||||||
/// into a "redirect" pointing at the other. These redirects form a
|
|
||||||
/// DAG: the roots of the DAG (nodes that are not redirected) are each
|
|
||||||
/// associated with a value of type `V` and a rank. The rank is used
|
|
||||||
/// to keep the DAG relatively balanced, which helps keep the running
|
|
||||||
/// time of the algorithm under control. For more information, see
|
|
||||||
/// <http://en.wikipedia.org/wiki/Disjoint-set_data_structure>.
|
|
||||||
#[derive(PartialEq, Clone, Debug)]
|
|
||||||
pub struct VarValue<K: UnifyKey> { // FIXME pub
|
|
||||||
parent: K, // if equal to self, this is a root
|
|
||||||
value: K::Value, // value assigned (only relevant to root)
|
|
||||||
rank: u32, // max depth (only relevant to root)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Table of unification keys and their values. You must define a key type K
|
|
||||||
/// that implements the `UnifyKey` trait. Unification tables can be used in two-modes:
|
|
||||||
///
|
|
||||||
/// - in-place (`UnificationTable<InPlace<K>>` or `InPlaceUnificationTable<K>`):
|
|
||||||
/// - This is the standard mutable mode, where the array is modified
|
|
||||||
/// in place.
|
|
||||||
/// - To do backtracking, you can employ the `snapshot` and `rollback_to`
|
|
||||||
/// methods.
|
|
||||||
/// - persistent (`UnificationTable<Persistent<K>>` or `PersistentUnificationTable<K>`):
|
|
||||||
/// - In this mode, we use a persistent vector to store the data, so that
|
|
||||||
/// cloning the table is an O(1) operation.
|
|
||||||
/// - This implies that ordinary operations are quite a bit slower though.
|
|
||||||
/// - Requires the `persistent` feature be selected in your Cargo.toml file.
|
|
||||||
#[derive(Clone, Debug, Default)]
|
|
||||||
pub struct UnificationTable<S: UnificationStore> {
|
|
||||||
/// Indicates the current value of each key.
|
|
||||||
values: S,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A unification table that uses an "in-place" vector.
|
|
||||||
#[allow(type_alias_bounds)]
|
|
||||||
pub type InPlaceUnificationTable<K: UnifyKey> = UnificationTable<InPlace<K>>;
|
|
||||||
|
|
||||||
/// A unification table that uses a "persistent" vector.
|
|
||||||
#[cfg(feature = "persistent")]
|
|
||||||
#[allow(type_alias_bounds)]
|
|
||||||
pub type PersistentUnificationTable<K: UnifyKey> = UnificationTable<Persistent<K>>;
|
|
||||||
|
|
||||||
/// At any time, users may snapshot a unification table. The changes
|
|
||||||
/// made during the snapshot may either be *committed* or *rolled back*.
|
|
||||||
pub struct Snapshot<S: UnificationStore> {
|
|
||||||
// Link snapshot to the unification store `S` of the table.
|
|
||||||
marker: marker::PhantomData<S>,
|
|
||||||
snapshot: S::Snapshot,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<K: UnifyKey> VarValue<K> {
|
|
||||||
fn new_var(key: K, value: K::Value) -> VarValue<K> {
|
|
||||||
VarValue::new(key, value, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new(parent: K, value: K::Value, rank: u32) -> VarValue<K> {
|
|
||||||
VarValue {
|
|
||||||
parent: parent, // this is a root
|
|
||||||
value: value,
|
|
||||||
rank: rank,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn redirect(&mut self, to: K) {
|
|
||||||
self.parent = to;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn root(&mut self, rank: u32, value: K::Value) {
|
|
||||||
self.rank = rank;
|
|
||||||
self.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parent(&self, self_key: K) -> Option<K> {
|
|
||||||
self.if_not_self(self.parent, self_key)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn if_not_self(&self, key: K, self_key: K) -> Option<K> {
|
|
||||||
if key == self_key {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// We can't use V:LatticeValue, much as I would like to,
|
|
||||||
// because frequently the pattern is that V=Option<U> for some
|
|
||||||
// other type parameter U, and we have no way to say
|
|
||||||
// Option<U>:LatticeValue.
|
|
||||||
|
|
||||||
impl<S: UnificationStore> UnificationTable<S> {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Starts a new snapshot. Each snapshot must be either
|
|
||||||
/// rolled back or committed in a "LIFO" (stack) order.
|
|
||||||
pub fn snapshot(&mut self) -> Snapshot<S> {
|
|
||||||
Snapshot {
|
|
||||||
marker: marker::PhantomData::<S>,
|
|
||||||
snapshot: self.values.start_snapshot(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reverses all changes since the last snapshot. Also
|
|
||||||
/// removes any keys that have been created since then.
|
|
||||||
pub fn rollback_to(&mut self, snapshot: Snapshot<S>) {
|
|
||||||
debug!("{}: rollback_to()", S::tag());
|
|
||||||
self.values.rollback_to(snapshot.snapshot);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Commits all changes since the last snapshot. Of course, they
|
|
||||||
/// can still be undone if there is a snapshot further out.
|
|
||||||
pub fn commit(&mut self, snapshot: Snapshot<S>) {
|
|
||||||
debug!("{}: commit()", S::tag());
|
|
||||||
self.values.commit(snapshot.snapshot);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a fresh key with the given value.
|
|
||||||
pub fn new_key(&mut self, value: S::Value) -> S::Key {
|
|
||||||
let len = self.values.len();
|
|
||||||
let key: S::Key = UnifyKey::from_index(len as u32);
|
|
||||||
self.values.push(VarValue::new_var(key, value));
|
|
||||||
debug!("{}: created new key: {:?}", S::tag(), key);
|
|
||||||
key
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reserve memory for `num_new_keys` to be created. Does not
|
|
||||||
/// actually create the new keys; you must then invoke `new_key`.
|
|
||||||
pub fn reserve(&mut self, num_new_keys: usize) {
|
|
||||||
self.values.reserve(num_new_keys);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Clears all unifications that have been performed, resetting to
|
|
||||||
/// the initial state. The values of each variable are given by
|
|
||||||
/// the closure.
|
|
||||||
pub fn reset_unifications(
|
|
||||||
&mut self,
|
|
||||||
mut value: impl FnMut(S::Key) -> S::Value,
|
|
||||||
) {
|
|
||||||
self.values.reset_unifications(|i| {
|
|
||||||
let key = UnifyKey::from_index(i as u32);
|
|
||||||
let value = value(key);
|
|
||||||
VarValue::new_var(key, value)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the number of keys created so far.
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.values.len()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Obtains the current value for a particular key.
|
|
||||||
/// Not for end-users; they can use `probe_value`.
|
|
||||||
fn value(&self, key: S::Key) -> &VarValue<S::Key> {
|
|
||||||
&self.values[key.index() as usize]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Find the root node for `vid`. This uses the standard
|
|
||||||
/// union-find algorithm with path compression:
|
|
||||||
/// <http://en.wikipedia.org/wiki/Disjoint-set_data_structure>.
|
|
||||||
///
|
|
||||||
/// NB. This is a building-block operation and you would probably
|
|
||||||
/// prefer to call `probe` below.
|
|
||||||
fn get_root_key(&mut self, vid: S::Key) -> S::Key {
|
|
||||||
let redirect = {
|
|
||||||
match self.value(vid).parent(vid) {
|
|
||||||
None => return vid,
|
|
||||||
Some(redirect) => redirect,
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let root_key: S::Key = self.get_root_key(redirect);
|
|
||||||
if root_key != redirect {
|
|
||||||
// Path compression
|
|
||||||
self.update_value(vid, |value| value.parent = root_key);
|
|
||||||
}
|
|
||||||
|
|
||||||
root_key
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_value<OP>(&mut self, key: S::Key, op: OP)
|
|
||||||
where
|
|
||||||
OP: FnOnce(&mut VarValue<S::Key>),
|
|
||||||
{
|
|
||||||
self.values.update(key.index() as usize, op);
|
|
||||||
debug!("Updated variable {:?} to {:?}", key, self.value(key));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Either redirects `node_a` to `node_b` or vice versa, depending
|
|
||||||
/// on the relative rank. The value associated with the new root
|
|
||||||
/// will be `new_value`.
|
|
||||||
///
|
|
||||||
/// NB: This is the "union" operation of "union-find". It is
|
|
||||||
/// really more of a building block. If the values associated with
|
|
||||||
/// your key are non-trivial, you would probably prefer to call
|
|
||||||
/// `unify_var_var` below.
|
|
||||||
fn unify_roots(&mut self, key_a: S::Key, key_b: S::Key, new_value: S::Value) {
|
|
||||||
debug!("unify(key_a={:?}, key_b={:?})", key_a, key_b);
|
|
||||||
|
|
||||||
let rank_a = self.value(key_a).rank;
|
|
||||||
let rank_b = self.value(key_b).rank;
|
|
||||||
if let Some((new_root, redirected)) =
|
|
||||||
S::Key::order_roots(
|
|
||||||
key_a,
|
|
||||||
&self.value(key_a).value,
|
|
||||||
key_b,
|
|
||||||
&self.value(key_b).value,
|
|
||||||
) {
|
|
||||||
// compute the new rank for the new root that they chose;
|
|
||||||
// this may not be the optimal choice.
|
|
||||||
let new_rank = if new_root == key_a {
|
|
||||||
debug_assert!(redirected == key_b);
|
|
||||||
if rank_a > rank_b {
|
|
||||||
rank_a
|
|
||||||
} else {
|
|
||||||
rank_b + 1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
debug_assert!(new_root == key_b);
|
|
||||||
debug_assert!(redirected == key_a);
|
|
||||||
if rank_b > rank_a {
|
|
||||||
rank_b
|
|
||||||
} else {
|
|
||||||
rank_a + 1
|
|
||||||
}
|
|
||||||
};
|
|
||||||
self.redirect_root(new_rank, redirected, new_root, new_value);
|
|
||||||
} else if rank_a > rank_b {
|
|
||||||
// a has greater rank, so a should become b's parent,
|
|
||||||
// i.e., b should redirect to a.
|
|
||||||
self.redirect_root(rank_a, key_b, key_a, new_value);
|
|
||||||
} else if rank_a < rank_b {
|
|
||||||
// b has greater rank, so a should redirect to b.
|
|
||||||
self.redirect_root(rank_b, key_a, key_b, new_value);
|
|
||||||
} else {
|
|
||||||
// If equal, redirect one to the other and increment the
|
|
||||||
// other's rank.
|
|
||||||
self.redirect_root(rank_a + 1, key_a, key_b, new_value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Internal method to redirect `old_root_key` (which is currently
|
|
||||||
/// a root) to a child of `new_root_key` (which will remain a
|
|
||||||
/// root). The rank and value of `new_root_key` will be updated to
|
|
||||||
/// `new_rank` and `new_value` respectively.
|
|
||||||
fn redirect_root(
|
|
||||||
&mut self,
|
|
||||||
new_rank: u32,
|
|
||||||
old_root_key: S::Key,
|
|
||||||
new_root_key: S::Key,
|
|
||||||
new_value: S::Value,
|
|
||||||
) {
|
|
||||||
self.update_value(old_root_key, |old_root_value| {
|
|
||||||
old_root_value.redirect(new_root_key);
|
|
||||||
});
|
|
||||||
self.update_value(new_root_key, |new_root_value| {
|
|
||||||
new_root_value.root(new_rank, new_value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// ////////////////////////////////////////////////////////////////////////
|
|
||||||
/// Public API
|
|
||||||
|
|
||||||
impl<'tcx, S, K, V> UnificationTable<S>
|
|
||||||
where
|
|
||||||
S: UnificationStore<Key = K, Value = V>,
|
|
||||||
K: UnifyKey<Value = V>,
|
|
||||||
V: Debug + Clone,
|
|
||||||
{
|
|
||||||
/// Unions two keys without the possibility of failure; only
|
|
||||||
/// applicable when unify values use `NoError` as their error
|
|
||||||
/// type.
|
|
||||||
pub fn union<K1, K2, F>(&mut self, a_id: K1, b_id: K2, unify_values: F)
|
|
||||||
where
|
|
||||||
K1: Into<K>,
|
|
||||||
K2: Into<K>,
|
|
||||||
V: Debug + Clone,
|
|
||||||
F: FnOnce(&Self, &V, &V) -> V
|
|
||||||
{
|
|
||||||
let a_id = a_id.into();
|
|
||||||
let b_id = b_id.into();
|
|
||||||
|
|
||||||
let root_a = self.get_root_key(a_id);
|
|
||||||
let root_b = self.get_root_key(b_id);
|
|
||||||
|
|
||||||
if root_a == root_b {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let combined = unify_values(&self, &self.value(root_a).value, &self.value(root_b).value);
|
|
||||||
|
|
||||||
self.unify_roots(root_a, root_b, combined);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Unions a key and a value without the possibility of failure.
|
|
||||||
pub fn union_value<K1, F>(&mut self, id: K1, value: V, unify_values: F)
|
|
||||||
where
|
|
||||||
K1: Into<K>,
|
|
||||||
V: Debug + Clone,
|
|
||||||
F: FnOnce(&Self, &V, &V) -> V
|
|
||||||
{
|
|
||||||
self.unify_var_value(id, value, unify_values);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Given two keys, indicates whether they have been unioned together.
|
|
||||||
pub fn unioned<K1, K2>(&mut self, a_id: K1, b_id: K2) -> bool
|
|
||||||
where
|
|
||||||
K1: Into<K>,
|
|
||||||
K2: Into<K>,
|
|
||||||
{
|
|
||||||
self.find(a_id) == self.find(b_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Given a key, returns the (current) root key.
|
|
||||||
pub fn find<K1>(&mut self, id: K1) -> K
|
|
||||||
where
|
|
||||||
K1: Into<K>,
|
|
||||||
{
|
|
||||||
let id = id.into();
|
|
||||||
self.get_root_key(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the value of the key `a_id` to `b`, attempting to merge
|
|
||||||
/// with the previous value.
|
|
||||||
pub fn unify_var_value<K1, F>(&mut self, a_id: K1, b: V, unify_values: F)
|
|
||||||
where
|
|
||||||
K1: Into<K>,
|
|
||||||
F: FnOnce(&Self, &V, &V) -> V
|
|
||||||
{
|
|
||||||
let a_id = a_id.into();
|
|
||||||
let root_a = self.get_root_key(a_id);
|
|
||||||
let value = unify_values(&self, &self.value(root_a).value, &b);
|
|
||||||
self.update_value(root_a, |node| node.value = value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the current value for the given key. If the key has
|
|
||||||
/// been union'd, this will give the value from the current root.
|
|
||||||
pub fn probe_value<K1>(&mut self, id: K1) -> V
|
|
||||||
where
|
|
||||||
K1: Into<K>,
|
|
||||||
{
|
|
||||||
let id = id.into();
|
|
||||||
let id = self.get_root_key(id);
|
|
||||||
self.value(id).value.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
44
old/expr.rs
44
old/expr.rs
|
@ -1,44 +0,0 @@
|
||||||
use name::Name;
|
|
||||||
use typ::Type;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub enum Operator {
|
|
||||||
Plus, Minus, Star, Slash, DoubleSlash,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub enum Builtin {
|
|
||||||
// Default
|
|
||||||
Negate,
|
|
||||||
Not,
|
|
||||||
|
|
||||||
// String
|
|
||||||
// StringLength,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Expr {
|
|
||||||
Int(i64),
|
|
||||||
Ratio(i64, u64),
|
|
||||||
|
|
||||||
// Functions
|
|
||||||
CallOperator(Box<Expr>, Operator, Box<Expr>),
|
|
||||||
CallBuiltin(Builtin, Box<Expr>),
|
|
||||||
CallLambda(Box<Expr>, Box<Expr>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Pattern {
|
|
||||||
Name(Name), // `foo =`
|
|
||||||
As(Name, Box<Pattern>), // `<pattern> as foo`
|
|
||||||
Type(Type),
|
|
||||||
Symbol(String),
|
|
||||||
String(String),
|
|
||||||
Char(char),
|
|
||||||
Int(i64),
|
|
||||||
Float(f64),
|
|
||||||
Tuple(Vec<Pattern>),
|
|
||||||
Record(Vec<(Name, Option<Pattern>)>), // { a = 5, b : Int as x, c }
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
use unify::Expr;
|
|
||||||
use unify::Literal;
|
|
||||||
|
|
||||||
pub fn eval<'a>(expr: &'a Expr<'a>) -> &'a Literal<'a> {
|
|
||||||
match expr {
|
|
||||||
Expr::Literal(literal) => literal,
|
|
||||||
Expr::Assignment(_, subexpr) => eval(subexpr),
|
|
||||||
Expr::If(cond, if_true, if_false) => {
|
|
||||||
match eval(cond) {
|
|
||||||
Literal::Symbol("True") => eval(if_true),
|
|
||||||
Literal::Symbol("False") => eval(if_false),
|
|
||||||
_ => {
|
|
||||||
panic!("somehow an if-conditional did not evaluate to True or False!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn literal_to_string<'a>(literal: &'a Literal<'a>) -> String {
|
|
||||||
match literal {
|
|
||||||
Literal::String(str) => format!("\"{}\"", str),
|
|
||||||
Literal::Char(character) => format!("'{}'", character),
|
|
||||||
Literal::Symbol(str) => str.to_string(),
|
|
||||||
Literal::HexOctalBinary(str) => str.to_string(),
|
|
||||||
Literal::Number(str) => str.to_string(),
|
|
||||||
Literal::Record(field_exprs) => {
|
|
||||||
let mut field_strings = Vec::new();
|
|
||||||
|
|
||||||
for (field, subexpr) in field_exprs {
|
|
||||||
let val = literal_to_string(eval(subexpr));
|
|
||||||
|
|
||||||
field_strings.push(format!("{} = {}", field, val));
|
|
||||||
}
|
|
||||||
|
|
||||||
format!("{{ {} }}", field_strings.join(", "))
|
|
||||||
},
|
|
||||||
Literal::Tuple(elem_exprs) => {
|
|
||||||
let mut elem_strings = Vec::new();
|
|
||||||
|
|
||||||
for elem_expr in elem_exprs {
|
|
||||||
elem_strings.push(literal_to_string(eval(elem_expr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
format!("({})", elem_strings.join(", "))
|
|
||||||
},
|
|
||||||
Literal::Array(elem_exprs) => {
|
|
||||||
let mut elem_strings = Vec::new();
|
|
||||||
|
|
||||||
for elem_expr in elem_exprs {
|
|
||||||
elem_strings.push(literal_to_string(eval(elem_expr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
format!("[{}]", elem_strings.join(", "))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
22
old/lib.rs
22
old/lib.rs
|
@ -1,22 +0,0 @@
|
||||||
#![feature(box_syntax, box_patterns)]
|
|
||||||
|
|
||||||
// pub mod unify;
|
|
||||||
// pub mod interpret;
|
|
||||||
// pub mod repl;
|
|
||||||
|
|
||||||
pub mod solve;
|
|
||||||
pub mod expr;
|
|
||||||
pub mod constrain;
|
|
||||||
pub mod canonical;
|
|
||||||
pub mod name;
|
|
||||||
pub mod typ;
|
|
||||||
pub mod parse;
|
|
||||||
mod ena;
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
|
|
||||||
#[cfg(feature = "persistent")]
|
|
||||||
extern crate dogged;
|
|
||||||
|
|
||||||
#[macro_use] extern crate combine;
|
|
|
@ -1 +0,0 @@
|
||||||
pub type Name = String;
|
|
335
old/parse.rs
335
old/parse.rs
|
@ -1,335 +0,0 @@
|
||||||
use expr::Operator;
|
|
||||||
use expr::Expr;
|
|
||||||
|
|
||||||
use std::char;
|
|
||||||
use std::iter;
|
|
||||||
|
|
||||||
use combine::parser::char::{char, string, letter, alpha_num, spaces, digit, hex_digit, HexDigit};
|
|
||||||
use combine::parser::repeat::{many, count_min_max};
|
|
||||||
use combine::parser::item::{any, satisfy, satisfy_map, value};
|
|
||||||
use combine::{choice, many1, parser, Parser, optional, between, unexpected_any};
|
|
||||||
use combine::error::{Consumed, ParseError};
|
|
||||||
use combine::stream::{Stream};
|
|
||||||
|
|
||||||
|
|
||||||
pub const ERR_EMPTY_CHAR: &'static str = "EMPTY_CHAR";
|
|
||||||
|
|
||||||
pub fn expr<I>() -> impl Parser<Input = I, Output = Expr>
|
|
||||||
where I: Stream<Item = char>,
|
|
||||||
I::Error: ParseError<I::Item, I::Range, I::Position>
|
|
||||||
{
|
|
||||||
// TODO change to expr() to reproduce rust compiler bug
|
|
||||||
expr_()
|
|
||||||
}
|
|
||||||
|
|
||||||
// This macro allows recursive parsers
|
|
||||||
parser! {
|
|
||||||
#[inline(always)]
|
|
||||||
fn expr_[I]()(I) -> Expr
|
|
||||||
where [ I: Stream<Item = char> ]
|
|
||||||
{
|
|
||||||
choice((
|
|
||||||
number_literal(),
|
|
||||||
ident(),
|
|
||||||
)).skip(spaces()).and(
|
|
||||||
// Optionally follow the expression with an operator,
|
|
||||||
//
|
|
||||||
// e.g. In the expression (1 + 2), the subexpression 1
|
|
||||||
// is followed by the operator + and another subexpression, 2
|
|
||||||
optional(
|
|
||||||
operator()
|
|
||||||
.skip(spaces())
|
|
||||||
.and(expr()
|
|
||||||
)
|
|
||||||
)).map(|(v1, maybe_op)| {
|
|
||||||
match maybe_op {
|
|
||||||
None => v1,
|
|
||||||
Some((op, v2)) => {
|
|
||||||
Expr::CallOperator(Box::new(v1), op, Box::new(v2))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn operator<I>() -> impl Parser<Input = I, Output = Operator>
|
|
||||||
where I: Stream<Item = char>,
|
|
||||||
I::Error: ParseError<I::Item, I::Range, I::Position>
|
|
||||||
{
|
|
||||||
choice((
|
|
||||||
char('+').map(|_| Operator::Plus),
|
|
||||||
char('-').map(|_| Operator::Minus),
|
|
||||||
char('*').map(|_| Operator::Star),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ident<I>() -> impl Parser<Input = I, Output = Expr>
|
|
||||||
where I: Stream<Item = char>,
|
|
||||||
I::Error: ParseError<I::Item, I::Range, I::Position>
|
|
||||||
{
|
|
||||||
char('.').map(|_| Expr::Int(1))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn string_literal<I>() -> impl Parser<Input = I, Output = Expr>
|
|
||||||
where I: Stream<Item = char>,
|
|
||||||
I::Error: ParseError<I::Item, I::Range, I::Position>
|
|
||||||
{
|
|
||||||
between(char('"'), char('"'), many(string_body()))
|
|
||||||
.map(|str| Expr::String(str))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn char_literal<I>() -> impl Parser<Input = I, Output = Expr>
|
|
||||||
where I: Stream<Item = char>,
|
|
||||||
I::Error: ParseError<I::Item, I::Range, I::Position>
|
|
||||||
{
|
|
||||||
between(char('\''), char('\''), char_body().expected(ERR_EMPTY_CHAR))
|
|
||||||
.map(|ch| Expr::Char(ch))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn unicode_code_pt<I>() -> impl Parser<Input = I, Output = char>
|
|
||||||
where
|
|
||||||
I: Stream<Item = char>,
|
|
||||||
I::Error: ParseError<I::Item, I::Range, I::Position>,
|
|
||||||
{
|
|
||||||
// You can put up to 6 hex digits inside \u{...}
|
|
||||||
// e.g. \u{00A0} or \u{101010}
|
|
||||||
// They must be no more than 10FFFF
|
|
||||||
let hex_code_pt =
|
|
||||||
count_min_max::<Vec<char>, HexDigit<I>>(1, 6, hex_digit())
|
|
||||||
.then(|hex_digits| {
|
|
||||||
let hex_str:String = hex_digits.into_iter().collect();
|
|
||||||
|
|
||||||
match u32::from_str_radix(&hex_str, 16) {
|
|
||||||
Ok(code_pt) => {
|
|
||||||
if code_pt > 0x10FFFF {
|
|
||||||
unexpected_any("Invalid Unicode code point. It must be no more than \\u{10FFFF}.").right()
|
|
||||||
} else {
|
|
||||||
match char::from_u32(code_pt) {
|
|
||||||
Some(ch) => value(ch).left(),
|
|
||||||
None => unexpected_any("Invalid Unicode code point.").right()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(_) => {
|
|
||||||
unexpected_any("Invalid hex code - Unicode code points must be specified using hexadecimal characters (the numbers 0-9 and letters A-F)").right()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
char('u').with(between(char('{'), char('}'), hex_code_pt))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn string_body<I>() -> impl Parser<Input = I, Output = char>
|
|
||||||
where
|
|
||||||
I: Stream<Item = char>,
|
|
||||||
I::Error: ParseError<I::Item, I::Range, I::Position>,
|
|
||||||
{
|
|
||||||
parser(|input: &mut I| {
|
|
||||||
let (parsed_char, consumed) = try!(any().parse_lazy(input).into());
|
|
||||||
let mut escaped = satisfy_map(|escaped_char| {
|
|
||||||
// NOTE! When modifying this, revisit char_body too!
|
|
||||||
// Their implementations are similar but not the same.
|
|
||||||
match escaped_char {
|
|
||||||
'"' => Some('"'),
|
|
||||||
'\\' => Some('\\'),
|
|
||||||
't' => Some('\t'),
|
|
||||||
'n' => Some('\n'),
|
|
||||||
'r' => Some('\r'),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
match parsed_char {
|
|
||||||
'\\' => {
|
|
||||||
consumed.combine(|_| {
|
|
||||||
// Try to parse basic backslash-escaped literals
|
|
||||||
// e.g. \t, \n, \r
|
|
||||||
escaped.parse_stream(input).or_else(|_|
|
|
||||||
// If we didn't find any of those, try \u{...}
|
|
||||||
unicode_code_pt().parse_stream(input)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
'"' => {
|
|
||||||
// We should never consume a double quote unless
|
|
||||||
// it's preceded by a backslash
|
|
||||||
Err(Consumed::Empty(I::Error::empty(input.position()).into()))
|
|
||||||
},
|
|
||||||
_ => Ok((parsed_char, consumed)),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn char_body<I>() -> impl Parser<Input = I, Output = char>
|
|
||||||
where
|
|
||||||
I: Stream<Item = char>,
|
|
||||||
I::Error: ParseError<I::Item, I::Range, I::Position>,
|
|
||||||
{
|
|
||||||
parser(|input: &mut I| {
|
|
||||||
let (parsed_char, consumed) = try!(any().parse_lazy(input).into());
|
|
||||||
let mut escaped = satisfy_map(|escaped_char| {
|
|
||||||
// NOTE! When modifying this, revisit string_body too!
|
|
||||||
// Their implementations are similar but not the same.
|
|
||||||
match escaped_char {
|
|
||||||
'\'' => Some('\''),
|
|
||||||
'\\' => Some('\\'),
|
|
||||||
't' => Some('\t'),
|
|
||||||
'n' => Some('\n'),
|
|
||||||
'r' => Some('\r'),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
match parsed_char {
|
|
||||||
'\\' => {
|
|
||||||
consumed.combine(|_| {
|
|
||||||
// Try to parse basic backslash-escaped literals
|
|
||||||
// e.g. \t, \n, \r
|
|
||||||
escaped.parse_stream(input).or_else(|_|
|
|
||||||
// If we didn't find any of those, try \u{...}
|
|
||||||
unicode_code_pt().parse_stream(input)
|
|
||||||
)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
'\'' => {
|
|
||||||
// We should never consume a single quote unless
|
|
||||||
// it's preceded by a backslash
|
|
||||||
Err(Consumed::Empty(I::Error::empty(input.position()).into()))
|
|
||||||
},
|
|
||||||
_ => Ok((parsed_char, consumed)),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn number_literal<I>() -> impl Parser<Input = I, Output = Expr>
|
|
||||||
where I: Stream<Item = char>,
|
|
||||||
I::Error: ParseError<I::Item, I::Range, I::Position>
|
|
||||||
{
|
|
||||||
// Digits before the decimal point can be space-separated
|
|
||||||
// e.g. one million can be written as 1 000 000
|
|
||||||
let digits_before_decimal = many1::<Vec<_>, _>(digit().skip(optional(char(' '))));
|
|
||||||
let digits_after_decimal = many1::<Vec<_>, _>(digit());
|
|
||||||
|
|
||||||
optional(char('-'))
|
|
||||||
.and(digits_before_decimal)
|
|
||||||
.and(optional(char('.').with(digits_after_decimal)))
|
|
||||||
.map(|((maybe_minus, int_digits), decimals): ((Option<char>, Vec<char>), Option<Vec<char>>)| {
|
|
||||||
let is_positive = maybe_minus.is_none();
|
|
||||||
|
|
||||||
// TODO check length of digits and make sure not to overflow
|
|
||||||
let int_str: String = int_digits.into_iter().collect();
|
|
||||||
let int_val = int_str.parse::<i64>().unwrap();
|
|
||||||
|
|
||||||
match decimals {
|
|
||||||
None => {
|
|
||||||
if is_positive {
|
|
||||||
Expr::Int(int_val as i64)
|
|
||||||
} else {
|
|
||||||
Expr::Int(-int_val as i64)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Some(nums) => {
|
|
||||||
let decimal_str: String = nums.into_iter().collect();
|
|
||||||
// calculate numerator and denominator
|
|
||||||
// e.g. 123.45 == 12345 / 100
|
|
||||||
let denom = (10 as i64).pow(decimal_str.len() as u32);
|
|
||||||
let decimal = decimal_str.parse::<u32>().unwrap();
|
|
||||||
let numerator = (int_val * denom) + (decimal as i64);
|
|
||||||
|
|
||||||
if is_positive {
|
|
||||||
Expr::Ratio(numerator, denom as u64)
|
|
||||||
} else {
|
|
||||||
Expr::Ratio(-numerator, denom as u64)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// pub fn parse_expr(state: &mut State) -> Result<Expr, Problem> {
|
|
||||||
|
|
||||||
// let digits = chomp_digits(state);
|
|
||||||
|
|
||||||
// if digits.is_empty() {
|
|
||||||
// Err(Problem::InvalidNumber)
|
|
||||||
// } else {
|
|
||||||
// // TODO store these in a bigint, and handle overflow.
|
|
||||||
// let num = digits.parse::<u32>().unwrap();
|
|
||||||
|
|
||||||
// if decimal_point
|
|
||||||
|
|
||||||
|
|
||||||
// Ok(Expr::Int(num))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// enum Parsed {
|
|
||||||
// Expr(Expr),
|
|
||||||
// Malformed(Problem),
|
|
||||||
// NotFound
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// #[inline]
|
|
||||||
// fn number_parser() -> {
|
|
||||||
// let has_minus_sign = false;
|
|
||||||
// let decimal_point_index: usize = 0;
|
|
||||||
// let len: usize = 0;
|
|
||||||
|
|
||||||
// for ch in state.text.chars() {
|
|
||||||
// if ch.is_ascii_digit() {
|
|
||||||
// len += 1;
|
|
||||||
// } else if ch == '-' {
|
|
||||||
// if has_minus_sign {
|
|
||||||
// if len == 1 {
|
|
||||||
// return Malformed(DoubleMinusSign);
|
|
||||||
// } else {
|
|
||||||
// // This second minus sign is a subtraction operator.
|
|
||||||
// // We've reached the end of the number!
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// has_minus_sign = true;
|
|
||||||
// len += 1;
|
|
||||||
// }
|
|
||||||
// } else if ch == '.' {
|
|
||||||
// if len == 0 {
|
|
||||||
// return Malformed(NoDigitsBeforeDecimalPoint);
|
|
||||||
// } else if decimal_point_index != 0 {
|
|
||||||
// return Malformed(DoubleDecimalPoint);
|
|
||||||
// } else {
|
|
||||||
// // This might be a valid decimal number!
|
|
||||||
// decimal_point_index = len;
|
|
||||||
|
|
||||||
// len += 1;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// state.col += len;
|
|
||||||
|
|
||||||
// if decimal_point_index == 0 {
|
|
||||||
// // This is an integer.
|
|
||||||
// Expr(Expr::Int(parse_int(&state.text[..len])))
|
|
||||||
// } else {
|
|
||||||
// // This is a decimal.
|
|
||||||
// let before_decimal_pt = &state.text[..decimal_point_index];
|
|
||||||
// let after_decimal_pt = &state.text[(decimal_point_index + 1)..];
|
|
||||||
|
|
||||||
// let numerator_str = before_decimal_pt.to_owned();
|
|
||||||
// numerator_str.push_str(after_decimal_pt);
|
|
||||||
|
|
||||||
// let numerator = parse_int(&numerator_str);
|
|
||||||
// let denominator = 10 * after_decimal_pt.len() as u64;
|
|
||||||
|
|
||||||
// Expr(Expr::Ratio(numerator, denominator))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[inline]
|
|
||||||
// fn parse_int(text: &str) -> i64 {
|
|
||||||
// // TODO parse as BigInt
|
|
||||||
// text.parse::<i64>().unwrap()
|
|
||||||
// }
|
|
||||||
|
|
63
old/repl.rs
63
old/repl.rs
|
@ -1,63 +0,0 @@
|
||||||
use interpret::{eval, literal_to_string};
|
|
||||||
use unify::infer;
|
|
||||||
use unify::Expr;
|
|
||||||
use unify::Type;
|
|
||||||
|
|
||||||
pub fn eval_and_print<'a>(expr: &Expr<'a>) -> String {
|
|
||||||
match infer(&expr) {
|
|
||||||
Ok(typ) => {
|
|
||||||
let lit = eval(expr);
|
|
||||||
|
|
||||||
format!("{}\n: {}", literal_to_string(lit), type_to_string(true, &typ))
|
|
||||||
},
|
|
||||||
Err(_) =>
|
|
||||||
"[TYPE MISMATCH!]".to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn type_to_string<'a>(outermost: bool, typ: &'a Type<'a>) -> String {
|
|
||||||
match typ {
|
|
||||||
Type::Unbound => "*".to_string(),
|
|
||||||
Type::String => "String".to_string(),
|
|
||||||
Type::Char => "Char".to_string(),
|
|
||||||
Type::Int => "Int".to_string(),
|
|
||||||
Type::Float => "Float".to_string(),
|
|
||||||
Type::Number => "Int | Float".to_string(),
|
|
||||||
Type::Symbol(sym) => format!(":{}", sym),
|
|
||||||
Type::Array(elem_type) => {
|
|
||||||
let str = format!("Array {}", type_to_string(false, elem_type));
|
|
||||||
|
|
||||||
if outermost {
|
|
||||||
str
|
|
||||||
} else {
|
|
||||||
format!("({})", str)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Type::Record(fields) => {
|
|
||||||
let field_strings = fields.into_iter().map(|(field, subtyp)| {
|
|
||||||
let typ_str = type_to_string(false, subtyp);
|
|
||||||
|
|
||||||
format!("{} : {}", field, typ_str)
|
|
||||||
});
|
|
||||||
|
|
||||||
format!("{{ {} }}", field_strings.collect::<Vec<String>>().join(", "))
|
|
||||||
},
|
|
||||||
Type::Tuple(elems) => {
|
|
||||||
let elem_strings = elems.into_iter().map(|subtyp| { type_to_string(false, subtyp) });
|
|
||||||
let str = elem_strings.collect::<Vec<String>>().join(", ");
|
|
||||||
|
|
||||||
if outermost {
|
|
||||||
str
|
|
||||||
} else {
|
|
||||||
format!("({})", str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Type::Assignment(_, assigned_typ) => type_to_string(outermost, assigned_typ),
|
|
||||||
Type::Union(set) => {
|
|
||||||
set.into_iter().collect::<Vec<&'a Type<'a>>>().into_iter().map(|typ_in_set| {
|
|
||||||
type_to_string(false, typ_in_set)
|
|
||||||
}).collect::<Vec<String>>().join(" | ")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
317
old/solve.rs
317
old/solve.rs
|
@ -1,317 +0,0 @@
|
||||||
use std::collections::BTreeSet;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use constrain::Constraint;
|
|
||||||
use typ::Type;
|
|
||||||
use canonical::Annotation;
|
|
||||||
use name::Name;
|
|
||||||
use self::Variable::*;
|
|
||||||
use ena::unify::{UnificationTable, UnifyKey, InPlace};
|
|
||||||
|
|
||||||
type UTable = UnificationTable<InPlace<VarId>>;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
enum Variable {
|
|
||||||
Wildcard,
|
|
||||||
RigidVar(Name),
|
|
||||||
FlexUnion(BTreeSet<VarId>),
|
|
||||||
RigidUnion(BTreeSet<VarId>),
|
|
||||||
Structure(FlatType),
|
|
||||||
Mismatch
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
enum FlatType {
|
|
||||||
Function(VarId, VarId),
|
|
||||||
|
|
||||||
// Apply a higher-kinded type constructor by name. For example:
|
|
||||||
// "Apply the higher-kinded type constructor `Array` to the variable `Int`
|
|
||||||
// to form `Array Int`."
|
|
||||||
// ApplyTypeConstructor(CanonicalModuleName, Name, VarId)
|
|
||||||
Tuple2(VarId, VarId),
|
|
||||||
Tuple3(VarId, VarId, VarId),
|
|
||||||
// TupleN(Vec<VarId>), // Last resort - allocates
|
|
||||||
// Record1 (Map.Map N.Name VarId) VarId,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn unify_rigid(named: &Variable, other: &Variable) -> Variable {
|
|
||||||
match other {
|
|
||||||
Wildcard => named.clone(),
|
|
||||||
RigidVar(_) => Mismatch,
|
|
||||||
FlexUnion(_) => Mismatch,
|
|
||||||
RigidUnion(_) => Mismatch,
|
|
||||||
Structure(_) => { panic!("TODO"); Mismatch }
|
|
||||||
Mismatch => other.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn unify_rigid_union(utable: &mut UTable, rigid_union: &BTreeSet<VarId>, var: &Variable, other: &Variable) -> Variable {
|
|
||||||
match other {
|
|
||||||
Wildcard => var.clone(),
|
|
||||||
RigidVar(_) => Mismatch,
|
|
||||||
FlexUnion(flex_union) => {
|
|
||||||
if rigid_union_fits_flex_union(utable, &rigid_union, &flex_union) {
|
|
||||||
var.clone()
|
|
||||||
} else {
|
|
||||||
Mismatch
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Structure(_) => { panic!("TODO"); Mismatch }
|
|
||||||
RigidUnion(_) => Mismatch,
|
|
||||||
Mismatch => other.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn rigid_union_fits_flex_union(utable: &mut UTable, rigid_union: &BTreeSet<VarId>, flex_union: &BTreeSet<VarId>) -> bool {
|
|
||||||
if rigid_union.is_subset(&flex_union) {
|
|
||||||
// If the keys of the rigid one are a subset of the flex keys, we're done.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let potentially_missing_flex_ids = flex_union.difference(rigid_union);
|
|
||||||
|
|
||||||
// a flex union can conform to a rigid one, as long
|
|
||||||
// as the rigid union contains all the flex union's alternative types
|
|
||||||
let rigid_union_values: BTreeSet<Variable> =
|
|
||||||
rigid_union.iter().map(|var_id| utable.probe_value(*var_id)).collect();
|
|
||||||
|
|
||||||
for flex_var_id in potentially_missing_flex_ids {
|
|
||||||
let flex_val = utable.probe_value(*flex_var_id);
|
|
||||||
|
|
||||||
if !rigid_union_values.contains(&flex_val) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn unify_flex_union(utable: &mut UTable, flex_union: &BTreeSet<VarId>, var: &Variable, other: &Variable) -> Variable {
|
|
||||||
match other {
|
|
||||||
Wildcard => var.clone(),
|
|
||||||
RigidVar(_) => Mismatch,
|
|
||||||
RigidUnion(rigid_union) => {
|
|
||||||
if rigid_union_fits_flex_union(utable, &rigid_union, &flex_union) {
|
|
||||||
other.clone()
|
|
||||||
} else {
|
|
||||||
Mismatch
|
|
||||||
}
|
|
||||||
},
|
|
||||||
FlexUnion(other_union) => unify_flex_unions(&flex_union, &other_union),
|
|
||||||
Structure(_) => unify_flex_union_with_structure(&flex_union, other),
|
|
||||||
Mismatch => other.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn unify_flex_unions(my_union: &BTreeSet<VarId>, other_union: &BTreeSet<VarId>) -> Variable {
|
|
||||||
let ids_in_common = my_union.intersection(other_union);
|
|
||||||
let unified_union: BTreeSet<VarId> = ids_in_common.into_iter().map(|var_id| *var_id).collect();
|
|
||||||
|
|
||||||
// If they have no types in common, that's a mismatch.
|
|
||||||
if unified_union.len() == 0 {
|
|
||||||
Mismatch
|
|
||||||
} else {
|
|
||||||
FlexUnion(unified_union)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unify_vars(utable: &mut UTable, first: &Variable, second: &Variable) -> Variable {
|
|
||||||
match first {
|
|
||||||
// wildcard types defer to whatever the other type happens to be.
|
|
||||||
Wildcard => second.clone(),
|
|
||||||
FlexUnion(union) => unify_flex_union(utable, &union, first, second),
|
|
||||||
RigidVar(Name) => unify_rigid(first, second),
|
|
||||||
RigidUnion(union) => unify_rigid_union(utable, &union, first, second),
|
|
||||||
Structure(flat_type) => unify_structure(utable, flat_type, first, second),
|
|
||||||
// Mismatches propagate.
|
|
||||||
Mismatch => first.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn unify_structure(utable: &mut UTable, flat_type: &FlatType, var: &Variable, other: &Variable) -> Variable {
|
|
||||||
match other {
|
|
||||||
Wildcard => var.clone(),
|
|
||||||
RigidVar(_) => Mismatch,
|
|
||||||
FlexUnion(flex_union) => unify_flex_union_with_structure(&flex_union, var),
|
|
||||||
RigidUnion(_) => Mismatch,
|
|
||||||
Structure(other_flat_type) => unify_flat_types(utable, flat_type, other_flat_type),
|
|
||||||
Mismatch => other.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn unify_flat_types(utable: &mut UTable, flat_type: &FlatType, other_flat_type: &FlatType) -> Variable {
|
|
||||||
match (flat_type, other_flat_type) {
|
|
||||||
(FlatType::Function(my_arg, my_return),
|
|
||||||
FlatType::Function(other_arg, other_return)) => {
|
|
||||||
let new_arg = unify_var_ids(utable, *my_arg, *other_arg);
|
|
||||||
let new_return = unify_var_ids(utable, *my_return, *other_return);
|
|
||||||
|
|
||||||
// Propagate any mismatches.
|
|
||||||
if new_arg == Mismatch {
|
|
||||||
new_arg
|
|
||||||
} else if new_return == Mismatch {
|
|
||||||
new_return
|
|
||||||
} else {
|
|
||||||
let new_arg_id = utable.new_key(new_arg);
|
|
||||||
let new_return_id = utable.new_key(new_return);
|
|
||||||
|
|
||||||
Structure(FlatType::Function(new_arg_id, new_return_id))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(FlatType::Function(_, __return), _) => Mismatch,
|
|
||||||
(_, FlatType::Function(_, __return)) => Mismatch,
|
|
||||||
(FlatType::Tuple2(my_first, my_second),
|
|
||||||
FlatType::Tuple2(other_first, other_second)) => {
|
|
||||||
let new_first = unify_var_ids(utable, *my_first, *other_first);
|
|
||||||
let new_second = unify_var_ids(utable, *my_second, *other_second);
|
|
||||||
|
|
||||||
// Propagate any mismatches.
|
|
||||||
if new_first == Mismatch {
|
|
||||||
new_first
|
|
||||||
} else if new_second == Mismatch {
|
|
||||||
new_second
|
|
||||||
} else {
|
|
||||||
let new_first_id = utable.new_key(new_first);
|
|
||||||
let new_second_id = utable.new_key(new_second);
|
|
||||||
|
|
||||||
Structure(FlatType::Tuple2(new_first_id, new_second_id))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(FlatType::Tuple2(_, _), _) => Mismatch,
|
|
||||||
(_, FlatType::Tuple2(_, _)) => Mismatch,
|
|
||||||
(FlatType::Tuple3(my_first, my_second, my_third),
|
|
||||||
FlatType::Tuple3(other_first, other_second, other_third)) => {
|
|
||||||
let new_first = unify_var_ids(utable, *my_first, *other_first);
|
|
||||||
let new_second = unify_var_ids(utable, *my_second, *other_second);
|
|
||||||
let new_third = unify_var_ids(utable, *my_third, *other_third);
|
|
||||||
|
|
||||||
// Propagate any mismatches.
|
|
||||||
if new_first == Mismatch {
|
|
||||||
new_first
|
|
||||||
} else if new_second == Mismatch {
|
|
||||||
new_second
|
|
||||||
} else if new_third == Mismatch {
|
|
||||||
new_third
|
|
||||||
} else {
|
|
||||||
let new_first_id = utable.new_key(new_first);
|
|
||||||
let new_second_id = utable.new_key(new_second);
|
|
||||||
let new_third_id = utable.new_key(new_third);
|
|
||||||
|
|
||||||
Structure(FlatType::Tuple3(new_first_id, new_second_id, new_third_id))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// (FlatType::Tuple3(_, _, _), _) => Mismatch,
|
|
||||||
// (_, FlatType::Tuple3(_, _, _)) => Mismatch,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn unify_flex_union_with_structure(flex_union: &BTreeSet<VarId>, var: &Variable) -> Variable {
|
|
||||||
// TODO I guess iterate through the set, looking up Variables
|
|
||||||
|
|
||||||
panic!("TODO");
|
|
||||||
// if flex_union.contains(var) {
|
|
||||||
// Narrow the union to the one member type
|
|
||||||
var.clone()
|
|
||||||
// } else {
|
|
||||||
// Mismatch
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Given a type, create a constraint variable for it and add it to the table.
|
|
||||||
// Return the VarId corresponding to the variable in the table.
|
|
||||||
fn type_to_var_id(utable: &mut UTable, typ: Type) -> VarId {
|
|
||||||
match typ {
|
|
||||||
Type::Call(box fn_type, box arg_type) => {
|
|
||||||
panic!("TODO");
|
|
||||||
utable.new_key(Mismatch)
|
|
||||||
// let left_var_id = type_to_var_id(utable, left_type);
|
|
||||||
// let right_var_id = type_to_var_id(utable, right_type);
|
|
||||||
|
|
||||||
// // TODO should we match on op to hardcode the types we expect?
|
|
||||||
// let flat_type = FlatType::Function(left_var_id, right_var_id);
|
|
||||||
|
|
||||||
// utable.new_key(Structure(flat_type))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
struct VarId(u32);
|
|
||||||
|
|
||||||
impl UnifyKey for VarId {
|
|
||||||
type Value = Variable;
|
|
||||||
|
|
||||||
fn index(&self) -> u32 { self.0 }
|
|
||||||
fn from_index(u: u32) -> VarId { VarId(u) }
|
|
||||||
|
|
||||||
// tag is a static string that's only used in debugging
|
|
||||||
fn tag() -> &'static str { "VarId" }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unify_var_ids(utable: &mut UTable, left_id: VarId, right_id: VarId) -> Variable {
|
|
||||||
let left_content = utable.probe_value(left_id);
|
|
||||||
let right_content = utable.probe_value(right_id);
|
|
||||||
|
|
||||||
if left_content == right_content {
|
|
||||||
left_content
|
|
||||||
} else {
|
|
||||||
unify_vars(utable, &left_content, &right_content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type TypeError = String;
|
|
||||||
|
|
||||||
pub fn solve_constraint(constraint: Constraint) -> Result<TypeError, HashMap<Name, Annotation>> {
|
|
||||||
let mut utable: UTable = UnificationTable::new();
|
|
||||||
|
|
||||||
solve(&mut utable, constraint);
|
|
||||||
|
|
||||||
Ok("TODO: actually gather errors etc".to_owned())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn solve(utable: &mut UTable, constraint: Constraint) {
|
|
||||||
match constraint {
|
|
||||||
Constraint::True => {},
|
|
||||||
|
|
||||||
Constraint::Equal(actual_type, expectation) => {
|
|
||||||
let actual_var_id = type_to_var_id(utable, actual_type);
|
|
||||||
let expected_var_id = type_to_var_id(utable, expectation);
|
|
||||||
let answer = unify_var_ids(utable, actual_var_id, expected_var_id);
|
|
||||||
|
|
||||||
panic!("Oh no! TYPE MISMATCH! (TODO: record errors as appropriate)");
|
|
||||||
()
|
|
||||||
// match answer {
|
|
||||||
// Mismatch => {
|
|
||||||
// panic!("Oh no! TYPE MISMATCH! (TODO: record errors as appropriate)");
|
|
||||||
// }
|
|
||||||
// do introduce rank pools vars
|
|
||||||
// return state
|
|
||||||
|
|
||||||
// UF.modify var $ \(Descriptor content _ mark copy) ->
|
|
||||||
// Descriptor content rank mark copy
|
|
||||||
|
|
||||||
// Unify.Err vars actualType expectedType ->
|
|
||||||
|
|
||||||
// panic!("TODO xyz");
|
|
||||||
// do introduce rank pools vars
|
|
||||||
// return $ addError state $
|
|
||||||
// Error.BadExpr region category actualType $
|
|
||||||
// Error.typeReplace expectation expectedType
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
|
|
||||||
Constraint::Batch(_) => {
|
|
||||||
panic!("TODO");
|
|
||||||
()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
12
old/typ.rs
12
old/typ.rs
|
@ -1,12 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub enum Type {
|
|
||||||
// Symbol(String),
|
|
||||||
// Int,
|
|
||||||
// Float,
|
|
||||||
// Number,
|
|
||||||
// TypeUnion(BTreeSet<Type>),
|
|
||||||
// Function(Box<Type>, Box<Type>),
|
|
||||||
Call(Box<Type>, Box<Type>),
|
|
||||||
}
|
|
363
old/unify.rs
363
old/unify.rs
|
@ -1,363 +0,0 @@
|
||||||
use std::collections::BTreeSet;
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use self::Type::*;
|
|
||||||
|
|
||||||
pub type Name<'a> = &'a str;
|
|
||||||
|
|
||||||
pub type ModuleName<'a> = &'a str;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub enum Type<'a> {
|
|
||||||
Unbound,
|
|
||||||
String,
|
|
||||||
Char,
|
|
||||||
Int,
|
|
||||||
Float,
|
|
||||||
Number,
|
|
||||||
Symbol(&'a str),
|
|
||||||
Array(Box<Type<'a>>),
|
|
||||||
Function(Box<Type<'a>>, Box<Type<'a>>),
|
|
||||||
Record(BTreeMap<Name<'a>, Type<'a>>),
|
|
||||||
Tuple(Vec<Type<'a>>),
|
|
||||||
Union(BTreeSet<Type<'a>>),
|
|
||||||
}
|
|
||||||
|
|
||||||
// CANONICAL IR - we have already done stuff like giving errors for
|
|
||||||
// duplicate field names
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Expr<'a> {
|
|
||||||
// Variables
|
|
||||||
Declaration(&'a Pattern<'a>, Box<&'a Expr<'a>>, Box<Expr<'a>>),
|
|
||||||
LookupLocal(&'a Name<'a>),
|
|
||||||
LookupGlobal(&'a ModuleName<'a>, &'a Name<'a>),
|
|
||||||
|
|
||||||
// Scalars
|
|
||||||
Symbol(&'a str),
|
|
||||||
String(&'a str),
|
|
||||||
Char(char),
|
|
||||||
HexOctalBinary(i64), // : Int
|
|
||||||
FractionalNumber(f64), // : Float
|
|
||||||
WholeNumber(i64), // : Int | Float
|
|
||||||
|
|
||||||
// Collections
|
|
||||||
Array(Vec<Expr<'a>>),
|
|
||||||
Record(Vec<(&'a Name<'a>, &'a Expr<'a>)>),
|
|
||||||
Tuple(Vec<&'a Expr<'a>>),
|
|
||||||
LookupName(Name<'a>, Box<&'a Expr<'a>>),
|
|
||||||
// TODO add record update
|
|
||||||
|
|
||||||
// Functions
|
|
||||||
Function(&'a Pattern<'a>, &'a Expr<'a>),
|
|
||||||
Call(Box<&'a Expr<'a>>, Box<&'a Expr<'a>>),
|
|
||||||
CallOperator(&'a Operator, Box<&'a Expr<'a>>, Box<&'a Expr<'a>>),
|
|
||||||
|
|
||||||
// Conditionals
|
|
||||||
If(Box<&'a Expr<'a>> /* Conditional */, Box<&'a Expr<'a>> /* True branch */, Box<&'a Expr<'a>> /* False branch */),
|
|
||||||
Case(Box<&'a Expr<'a>>, Vec<(&'a Pattern<'a>, &'a Expr<'a>)>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Operator {
|
|
||||||
Plus, Minus, Star, Caret, Percent, FloatDivision, IntDivision,
|
|
||||||
GT, GTE, LT, LTE,
|
|
||||||
EQ, NE, And, Or,
|
|
||||||
QuestionMark, Or
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Pattern<'a> {
|
|
||||||
Name(&'a Name<'a>), // `foo =`
|
|
||||||
As(&'a Name<'a>, &'a Pattern<'a>), // `<pattern> as foo`
|
|
||||||
Type(&'a Type<'a>),
|
|
||||||
Symbol(&'a str),
|
|
||||||
String(&'a str),
|
|
||||||
Char(char),
|
|
||||||
WholeNumber(&'a str),
|
|
||||||
FractionalNumber(&'a str),
|
|
||||||
HexOctalBinary(&'a str),
|
|
||||||
Tuple(Vec<Pattern<'a>>),
|
|
||||||
Record(Vec<(Name<'a>, Option<Pattern<'a>>)>), // { a = 5, b : Int as x, c }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn infer<'a>(expr: &Expr<'a>) -> Result<Type<'a>, UnificationProblem> {
|
|
||||||
match expr {
|
|
||||||
Expr::String(_) => Ok(String),
|
|
||||||
Expr::Char(_) => Ok(Char),
|
|
||||||
Expr::HexOctalBinary(_) => Ok(Int),
|
|
||||||
Expr::FractionalNumber(_) => Ok(Float),
|
|
||||||
Expr::WholeNumber(_) => Ok(Number),
|
|
||||||
Expr::Symbol(sym) => Ok(Symbol(sym)),
|
|
||||||
Expr::Array(elem_exprs) => {
|
|
||||||
let elem_type;
|
|
||||||
|
|
||||||
if elem_exprs.is_empty() {
|
|
||||||
elem_type = Unbound;
|
|
||||||
} else {
|
|
||||||
let mut unified_type = BTreeSet::new();
|
|
||||||
|
|
||||||
// Unify the types of all the elements
|
|
||||||
for elem_expr in elem_exprs {
|
|
||||||
unified_type.insert(infer(&elem_expr)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
if unified_type.len() == 1 {
|
|
||||||
// No point in storing a union of 1.
|
|
||||||
elem_type = unified_type.into_iter().next().unwrap()
|
|
||||||
} else {
|
|
||||||
elem_type = Union(unified_type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Array(Box::new(elem_type)))
|
|
||||||
},
|
|
||||||
Expr::Record(fields) => {
|
|
||||||
let mut rec_type: BTreeMap<&'a Name<'a>, Type<'a>> = BTreeMap::new();
|
|
||||||
|
|
||||||
for (field, subexpr) in fields {
|
|
||||||
let field_type = infer(subexpr)?;
|
|
||||||
|
|
||||||
rec_type.insert(&field, field_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Record(rec_type))
|
|
||||||
},
|
|
||||||
Expr::Tuple(exprs) => {
|
|
||||||
let mut tuple_type: Vec<Type<'a>> = Vec::new();
|
|
||||||
|
|
||||||
for subexpr in exprs {
|
|
||||||
let field_type = infer(subexpr)?;
|
|
||||||
|
|
||||||
tuple_type.push(field_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Tuple(tuple_type))
|
|
||||||
},
|
|
||||||
Expr::If(box cond, expr_if_true, expr_if_false) => {
|
|
||||||
let cond_type = infer(&cond)?;
|
|
||||||
|
|
||||||
// if-conditionals must be of type Bool
|
|
||||||
if !matches_bool_type(&cond_type) {
|
|
||||||
return Err(UnificationProblem::IfConditionNotBool);
|
|
||||||
}
|
|
||||||
|
|
||||||
// unify the true and false branches
|
|
||||||
let true_type = infer(&expr_if_true)?;
|
|
||||||
let false_type = infer(&expr_if_false)?;
|
|
||||||
|
|
||||||
let mut unified_type = BTreeSet::new();
|
|
||||||
|
|
||||||
unified_type.insert(true_type);
|
|
||||||
unified_type.insert(false_type);
|
|
||||||
|
|
||||||
if unified_type.len() == 1 {
|
|
||||||
// No point in storing a union of 1.
|
|
||||||
//
|
|
||||||
// We can't reuse true_type because it's been moved into the set
|
|
||||||
// but we can pull it back out of the set
|
|
||||||
Ok(unified_type.into_iter().next().unwrap())
|
|
||||||
} else {
|
|
||||||
Ok(Union(unified_type))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Call(func, arg) => {
|
|
||||||
|
|
||||||
},
|
|
||||||
CallOperator(op, left_expr, right_expr) => {
|
|
||||||
let left = &(infer(left_expr)?);
|
|
||||||
let right = &(infer(right_expr)?);
|
|
||||||
|
|
||||||
match op {
|
|
||||||
Operator::EQ | Operator::NE | Operator::And | Operator::Or => {
|
|
||||||
if types_match(left, right) {
|
|
||||||
conform_to_bool(left)
|
|
||||||
} else {
|
|
||||||
Err(UnificationProblem::TypeMismatch)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Operator::Plus | Operator::Minus | Operator::Star
|
|
||||||
| Operator::GT | Operator::LT | Operator::GTE | Operator::LTE
|
|
||||||
| Operator::Caret | Operator::Percent => {
|
|
||||||
if types_match(left, right) {
|
|
||||||
conform_to_number(left)
|
|
||||||
} else {
|
|
||||||
Err(UnificationProblem::TypeMismatch)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Operator::FloatDivision => {
|
|
||||||
if matches_float_type(left) && matches_float_type(right) {
|
|
||||||
Ok(&Float)
|
|
||||||
} else {
|
|
||||||
Err(UnificationProblem::TypeMismatch)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Operator::IntDivision => {
|
|
||||||
if matches_int_type(left) && matches_int_type(right) {
|
|
||||||
Ok(&Int)
|
|
||||||
} else {
|
|
||||||
Err(UnificationProblem::TypeMismatch)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Operator::CombineStrings => {
|
|
||||||
if matches_string_type(left) && matches_string_type(right) {
|
|
||||||
Ok(&String)
|
|
||||||
} else {
|
|
||||||
Err(UnificationProblem::TypeMismatch)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Operator::QuestionMark => {
|
|
||||||
if types_match(left, right) {
|
|
||||||
conform_to_optional(left)
|
|
||||||
} else {
|
|
||||||
Err(UnificationProblem::TypeMismatch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Expr::Declaration(pattern, let_expr, in_expr) => {
|
|
||||||
// Think of this as a let..in even though syntactically it's not.
|
|
||||||
// We need to type-check the let-binding, but the type of the
|
|
||||||
// *expression* we're expaning is only affected by the in-block.
|
|
||||||
check_pattern(&pattern, &let_expr)?;
|
|
||||||
|
|
||||||
infer(in_expr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn types_match<'a>(first: &'a Type<'a>, second: &'a Type<'a>) -> bool {
|
|
||||||
match (first, second) {
|
|
||||||
(Type::Union(first_types), Type::Union(second_types)) => {
|
|
||||||
// If any type is not directly present in the other union,
|
|
||||||
// it must at least match *some* type in the other union
|
|
||||||
first_types.difference(second_types).into_iter().all(|not_in_second_type| {
|
|
||||||
second_types.iter().any(|second_type| types_match(second_type, not_in_second_type))
|
|
||||||
}) &&
|
|
||||||
second_types.difference(first_types).into_iter().all(|not_in_first_type| {
|
|
||||||
first_types.iter().any(|first_type| types_match(first_type, not_in_first_type))
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
// Happy path: try these first, since we expect them to succeed.
|
|
||||||
// These are sorted based on a vague guess of how often they will be used in practice.
|
|
||||||
(Type::Symbol(sym_one), Type::Symbol(sym_two)) => sym_one == sym_two,
|
|
||||||
(Type::String, Type::String) => true,
|
|
||||||
(Type::Unbound, _) | (_, Type::Unbound)=> true,
|
|
||||||
(Type::Array(box elem_type_one), Type::Array(box elem_type_two)) => {
|
|
||||||
types_match(elem_type_one, elem_type_two)
|
|
||||||
},
|
|
||||||
(Type::Number, Type::Number) => true,
|
|
||||||
(Type::Number, other) => matches_number_type(other),
|
|
||||||
(other, Type::Number) => matches_number_type(other),
|
|
||||||
(Type::Int, Type::Int) => true,
|
|
||||||
(Type::Float, Type::Float) => true,
|
|
||||||
(Type::Tuple(first_elems), Type::Tuple(second_elems)) => {
|
|
||||||
// TODO verify that the elems and their types match up
|
|
||||||
// TODO write some scenarios to understand these better -
|
|
||||||
// like, what happens if you have a function that takes
|
|
||||||
// a lambda whose argument takes an open record,
|
|
||||||
// and you pass a lamba whose argument takes *fewer* fields?
|
|
||||||
// that should work! the function is gonna pass it a lambda that
|
|
||||||
// has more fields than it needs.
|
|
||||||
// I think there's an element of directionality here that I'm
|
|
||||||
// disregarding. Maybe this function shouldn't commute.
|
|
||||||
},
|
|
||||||
(Type::Function(first_arg), Type::Function(second_arg)) => {
|
|
||||||
// TODO verify that the elems and their types match up
|
|
||||||
},
|
|
||||||
(Type::Record(first_fields), Type::Record(second_fields)) => {
|
|
||||||
// TODO verify that the fields and their types match up
|
|
||||||
// TODO what should happen if one is a superset of the other? fail?
|
|
||||||
},
|
|
||||||
(Type::Char, Type::Char) => true,
|
|
||||||
|
|
||||||
// Unhappy path - expect these to fail, so check them last
|
|
||||||
(Type::Union(first_types), _) => {
|
|
||||||
first_types.iter().all(|typ| types_match(typ, second))
|
|
||||||
},
|
|
||||||
(_, Type::Union(second_types)) => {
|
|
||||||
second_types.iter().all(|typ| types_match(first, typ))
|
|
||||||
},
|
|
||||||
(Type::String, _) | (_, Type::String) => false,
|
|
||||||
(Type::Char, _) | (_, Type::Char) => false,
|
|
||||||
(Type::Int, _) | (_, Type::Int) => false,
|
|
||||||
(Type::Float, _) | (_, Type::Float) => false,
|
|
||||||
(Type::Symbol(_), _) | (_, Type::Symbol(_)) => false,
|
|
||||||
(Type::Array(_), _) | (_, Type::Array(_)) => false,
|
|
||||||
(Type::Record(_), _) | (_, Type::Record(_)) => false,
|
|
||||||
(Type::Tuple(_), _) | (_, Type::Tuple(_)) => false,
|
|
||||||
(Type::Function(_, _), _) | (_, Type::Function(_, _)) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn check_pattern<'a>(pattern: &'a Pattern<'a>, expr: &'a Expr<'a>) -> Result<(), UnificationProblem> {
|
|
||||||
let expr_type = infer(expr)?;
|
|
||||||
|
|
||||||
panic!("TODO check the pattern's type against expr_type, then write some tests for funky record pattern cases - this is our first real unification! Next one will be field access, ooooo - gonna want lots of tests for that")
|
|
||||||
}
|
|
||||||
|
|
||||||
const TRUE_SYMBOL_STR: &'static str = "True";
|
|
||||||
const FALSE_SYMBOL_STR: &'static str = "False";
|
|
||||||
|
|
||||||
pub fn matches_string_type<'a>(candidate: &Type<'a>) -> bool {
|
|
||||||
match candidate {
|
|
||||||
Unbound | String => true,
|
|
||||||
Type::Union(types) => {
|
|
||||||
types.iter().all(|typ| matches_string_type(typ))
|
|
||||||
},
|
|
||||||
_ => Err(UnificationProblem::TypeMismatch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn matches_bool_type<'a>(candidate: &Type<'a>) -> bool {
|
|
||||||
match candidate {
|
|
||||||
Type::Unbound => true,
|
|
||||||
Type::Symbol(str) => str == &TRUE_SYMBOL_STR || str == &FALSE_SYMBOL_STR,
|
|
||||||
Type::Union(types) => {
|
|
||||||
types.iter().all(|typ| matches_bool_type(typ))
|
|
||||||
}
|
|
||||||
_ => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn matches_number_type<'a>(candidate: &Type<'a>) -> bool {
|
|
||||||
match candidate {
|
|
||||||
Type::Unbound | Type::Int | Type::Float | Type::Number => true,
|
|
||||||
Type::Union(types) => {
|
|
||||||
types.iter().all(|typ| matches_number_type(typ))
|
|
||||||
}
|
|
||||||
_ => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn matches_int_type<'a>(candidate: &Type<'a>) -> bool {
|
|
||||||
match candidate {
|
|
||||||
Type::Unbound | Type::Int => true,
|
|
||||||
Type::Union(types) => {
|
|
||||||
types.iter().all(|typ| matches_int_type(typ))
|
|
||||||
}
|
|
||||||
_ => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn matches_float_type<'a>(candidate: &Type<'a>) -> bool {
|
|
||||||
match candidate {
|
|
||||||
Type::Unbound | Type::Float => true,
|
|
||||||
Type::Union(types) => {
|
|
||||||
types.iter().all(|typ| matches_float_type(typ))
|
|
||||||
}
|
|
||||||
_ => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum UnificationProblem {
|
|
||||||
CannotUnifyAssignments,
|
|
||||||
NotMemberOfUnion,
|
|
||||||
TypeMismatch,
|
|
||||||
IfConditionNotBool,
|
|
||||||
SymbolMismatch
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,38 +0,0 @@
|
||||||
#[macro_use] extern crate pretty_assertions;
|
|
||||||
|
|
||||||
extern crate roc;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod interpreter_tests {
|
|
||||||
use roc::interpret::literal_to_string;
|
|
||||||
use roc::unify::Expr::Literal;
|
|
||||||
use roc::unify::Literal::{String, Record};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_string_literal() {
|
|
||||||
let expected = "\"hi!\"";
|
|
||||||
let literal = String("hi!");
|
|
||||||
|
|
||||||
assert_eq!(expected, literal_to_string(&literal));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_record_literal() {
|
|
||||||
let str0 = &String("blah");
|
|
||||||
let str1 = &String("foo");
|
|
||||||
let str2 = &String("bar");
|
|
||||||
|
|
||||||
let x = ("x", &Literal(str1));
|
|
||||||
let y = ("y", &Literal(str2));
|
|
||||||
|
|
||||||
let subrec = &Record(vec![x, y]);
|
|
||||||
let str_pair = ("string", &Literal(str0));
|
|
||||||
let rec_pair = ("record", &Literal(subrec));
|
|
||||||
let toprec = vec![str_pair, rec_pair];
|
|
||||||
let literal = &Record(toprec);
|
|
||||||
|
|
||||||
let expected = "{ string = \"blah\", record = { x = \"foo\", y = \"bar\" } }";
|
|
||||||
|
|
||||||
assert_eq!(expected, literal_to_string(literal));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
#[macro_use] extern crate pretty_assertions;
|
|
||||||
|
|
||||||
extern crate roc;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod repl_tests {
|
|
||||||
use roc::repl::eval_and_print;
|
|
||||||
use roc::unify::Expr::{Literal};
|
|
||||||
use roc::unify::Literal::{String, Record};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_eval_and_print() {
|
|
||||||
let expected = "\"hi!\"\n: String";
|
|
||||||
let literal = String("hi!");
|
|
||||||
let expr = Literal(&literal);
|
|
||||||
|
|
||||||
assert_eq!(expected, eval_and_print(&expr));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_record_literal() {
|
|
||||||
let expected = "{ string = \"abc\", record = { x = \"one\", y = \"two\" } }\n: { string : String, record : { x : String, y : String } }";
|
|
||||||
|
|
||||||
let str0 = &String("abc");
|
|
||||||
let str1 = &String("one");
|
|
||||||
let str2 = &String("two");
|
|
||||||
|
|
||||||
let x = ("x", &Literal(str1));
|
|
||||||
let y = ("y", &Literal(str2));
|
|
||||||
|
|
||||||
let subrec = &Record(vec![x, y]);
|
|
||||||
let str_pair = ("string", &Literal(str0));
|
|
||||||
let rec_pair = ("record", &Literal(subrec));
|
|
||||||
let toprec = vec![str_pair, rec_pair];
|
|
||||||
let literal = &Record(toprec);
|
|
||||||
|
|
||||||
let expr = Literal(&literal);
|
|
||||||
|
|
||||||
assert_eq!(expected, eval_and_print(&expr));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,134 +0,0 @@
|
||||||
#[macro_use] extern crate pretty_assertions;
|
|
||||||
|
|
||||||
extern crate roc;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use roc::solve::solve_constraint;
|
|
||||||
use roc::typ::Type::*;
|
|
||||||
use roc::constrain::Constraint::*;
|
|
||||||
use roc::expr::Expr::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_solve_true() {
|
|
||||||
let expected = HashMap::new();
|
|
||||||
|
|
||||||
assert_eq!(Ok(expected), solve_constraint(True));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_solve_unify_basic() {
|
|
||||||
let expected = HashMap::new();
|
|
||||||
|
|
||||||
// TODO unify a function call.
|
|
||||||
// TODO to do this, will nee to introduce let-bindings to put stuff in the Name Map
|
|
||||||
// TODO since function calls are looked up by name.
|
|
||||||
let type_to_unify:Type = ...
|
|
||||||
let expected_type_to_unify:ExpectedType = ...
|
|
||||||
|
|
||||||
assert_eq!(Ok(expected), solve_constraint(Unify(type_to_unify, expected_type_to_unify));
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_negate_number() {
|
|
||||||
// expect_type(Type::Number, CallBuiltin(Negate, WholeNumber(5)));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_negate_float() {
|
|
||||||
// expect_type(Type::Float, CallBuiltin(Negate, FractinalNumber(3.1)));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_negate_int_twice() {
|
|
||||||
// expect_type(Type::Int, negate_twice(HexOctalBinary(0x12)));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_negate_number_twice() {
|
|
||||||
// expect_type(Type::Number, negate_twice(WholeNumber(5)));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_negate_float_twice() {
|
|
||||||
// expect_type(Type::Float, negate_twice(FractinalNumber(3.1)));
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_int_literal() {
|
|
||||||
// expect_type(Type::Int, HexOctalBinary(0x12));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_float_literal() {
|
|
||||||
// expect_type(Type::Float, FractionalNumber(3.1));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn test_number_literal() {
|
|
||||||
// expect_type(Type::Number, WholeNumber(5));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn add_ints_returns_int() {
|
|
||||||
// expect_type(Type::Int, CallOperator(Plus, int(), int()));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn add_floats_returns_float() {
|
|
||||||
// expect_type(Type::Float, CallOperator(Plus, float(), float()));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn add_nums_returns_num() {
|
|
||||||
// expect_type(Type::Number, CallOperator(Plus, num(), num()));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn add_num_int_returns_int() {
|
|
||||||
// expect_type(Type::Int, CallOperator(Plus, num(), int()));
|
|
||||||
// expect_type(Type::Int, CallOperator(Plus, int(), num()));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn add_num_float_returns_float() {
|
|
||||||
// expect_type(Type::Float, CallOperator(Plus, num(), float()));
|
|
||||||
// expect_type(Type::Float, CallOperator(Plus, float(), num()));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[test]
|
|
||||||
// fn add_int_float_returns_mismatch() {
|
|
||||||
// expect_mismatch(CallOperator(Plus, int(), float()));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn expect_type<'a>(expected_type: Type<'a>, expr: Expr<'a>) {
|
|
||||||
// assert_eq!(expected_type, infer_type(expr).unwrap());
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn expect_mismatch<'a>(expr: Expr<'a>) {
|
|
||||||
// assert_eq!(Err(Problem::Mismatch), infer_type(expr));
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[inline]
|
|
||||||
// fn negate_twice(expr) {
|
|
||||||
// CallBuiltin(Negate, CallBuiltin(Negate, expr))
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fn int<'a>() -> Box<&'a Expr<'a>> { Box::new(&HexOctalBinary(0x12)) }
|
|
||||||
// fn float<'a>() -> Box<&'a Expr<'a>> { Box::new(&FractionalNumber(3.1)) }
|
|
||||||
// fn num<'a>() -> Box<&'a Expr<'a>> { Box::new(&WholeNumber(5)) }
|
|
||||||
|
|
||||||
// TODO test unions that ought to be equivalent, but only after
|
|
||||||
// a reduction of some sort, e.g.
|
|
||||||
//
|
|
||||||
// ((a|b)|c) vs (a|(b|c))
|
|
||||||
//
|
|
||||||
// ((a|z)|(b|z)) vs (a|b|z)
|
|
||||||
//
|
|
||||||
// ideally, we fix these when constructing unions
|
|
||||||
// e.g. if a user puts this in as an annotation, reduce it immediately
|
|
||||||
// and when we're inferring unions, always infer them flat.
|
|
||||||
// This way we can avoid checking recursively.
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
#[macro_use] extern crate pretty_assertions;
|
|
||||||
|
|
||||||
extern crate roc;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use roc::unify::Type;
|
|
||||||
use roc::unify::Expr::Literal;
|
|
||||||
use roc::unify::Literal::{String, Record};
|
|
||||||
use roc::unify::infer;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_infer_record_literals() {
|
|
||||||
let str0 = &String("doesn't matter");
|
|
||||||
let str1 = &String("ignored");
|
|
||||||
let str2 = &String("also ignored");
|
|
||||||
|
|
||||||
let x = ("x", &Literal(str1));
|
|
||||||
let y = ("y", &Literal(str2));
|
|
||||||
|
|
||||||
let subrec = &Record(vec![x, y]);
|
|
||||||
let str_pair = ("string", &Literal(str0));
|
|
||||||
let rec_pair = ("record", &Literal(subrec));
|
|
||||||
let toprec = vec![str_pair, rec_pair];
|
|
||||||
let literal = &Record(toprec);
|
|
||||||
|
|
||||||
let expr = Literal(literal);
|
|
||||||
|
|
||||||
let expected_type = Type::Record(vec![
|
|
||||||
("string", Type::String),
|
|
||||||
("record", Type::Record(vec![
|
|
||||||
("x", Type::String),
|
|
||||||
("y", Type::String)
|
|
||||||
]))
|
|
||||||
]);
|
|
||||||
|
|
||||||
assert_eq!(expected_type, infer(&expr).unwrap());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
use typ::Type;
|
|
||||||
|
|
||||||
// constrainDecls :: Can.Decls -> Constraint -> IO Constraint
|
|
||||||
// constrainDecls decls finalConstraint =
|
|
||||||
// case decls of
|
|
||||||
// Can.Declare def otherDecls ->
|
|
||||||
// Expr.constrainDef Map.empty def =<< constrainDecls otherDecls finalConstraint
|
|
||||||
|
|
||||||
// Can.DeclareRec defs otherDecls ->
|
|
||||||
// Expr.constrainRecursiveDefs Map.empty defs =<< constrainDecls otherDecls finalConstraint
|
|
||||||
|
|
||||||
// Can.SaveTheEnvironment ->
|
|
||||||
// return finalConstraint
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
pub type ExpectedType = Type;
|
|
||||||
|
|
||||||
|
|
||||||
pub enum Constraint {
|
|
||||||
True,
|
|
||||||
Equal(Type, ExpectedType),
|
|
||||||
Batch(Vec<Constraint>),
|
|
||||||
}
|
|
55
src/expr.rs
55
src/expr.rs
|
@ -1,44 +1,21 @@
|
||||||
use name::Name;
|
|
||||||
use typ::Type;
|
#[derive(Debug, PartialEq)]
|
||||||
|
pub enum Expr {
|
||||||
|
// Literals
|
||||||
|
Int(i64),
|
||||||
|
Frac(i64, u64),
|
||||||
|
String(String),
|
||||||
|
Char(char),
|
||||||
|
|
||||||
|
Var(String),
|
||||||
|
|
||||||
|
// Functions
|
||||||
|
Func(String, Box<Expr>),
|
||||||
|
Apply(Box<Expr>, Box<Expr>),
|
||||||
|
Operator(Box<Expr>, Operator, Box<Expr>),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum Operator {
|
pub enum Operator {
|
||||||
Plus, Minus, Star, Slash, DoubleSlash,
|
Plus, Minus, Star, Slash, DoubleSlash,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub enum Builtin {
|
|
||||||
// Default
|
|
||||||
Negate,
|
|
||||||
Not,
|
|
||||||
|
|
||||||
// String
|
|
||||||
// StringLength,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Expr {
|
|
||||||
Int(i64),
|
|
||||||
Ratio(i64, u64),
|
|
||||||
|
|
||||||
// Functions
|
|
||||||
CallOperator(Box<Expr>, Operator, Box<Expr>),
|
|
||||||
CallBuiltin(Builtin, Box<Expr>),
|
|
||||||
CallLambda(Box<Expr>, Box<Expr>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Pattern {
|
|
||||||
Name(Name), // `foo =`
|
|
||||||
As(Name, Box<Pattern>), // `<pattern> as foo`
|
|
||||||
Type(Type),
|
|
||||||
Symbol(String),
|
|
||||||
String(String),
|
|
||||||
Char(char),
|
|
||||||
Int(i64),
|
|
||||||
Float(f64),
|
|
||||||
Tuple(Vec<Pattern>),
|
|
||||||
Record(Vec<(Name, Option<Pattern>)>), // { a = 5, b : Int as x, c }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
use unify::Expr;
|
|
||||||
use unify::Literal;
|
|
||||||
|
|
||||||
pub fn eval<'a>(expr: &'a Expr<'a>) -> &'a Literal<'a> {
|
|
||||||
match expr {
|
|
||||||
Expr::Literal(literal) => literal,
|
|
||||||
Expr::Assignment(_, subexpr) => eval(subexpr),
|
|
||||||
Expr::If(cond, if_true, if_false) => {
|
|
||||||
match eval(cond) {
|
|
||||||
Literal::Symbol("True") => eval(if_true),
|
|
||||||
Literal::Symbol("False") => eval(if_false),
|
|
||||||
_ => {
|
|
||||||
panic!("somehow an if-conditional did not evaluate to True or False!")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn literal_to_string<'a>(literal: &'a Literal<'a>) -> String {
|
|
||||||
match literal {
|
|
||||||
Literal::String(str) => format!("\"{}\"", str),
|
|
||||||
Literal::Char(character) => format!("'{}'", character),
|
|
||||||
Literal::Symbol(str) => str.to_string(),
|
|
||||||
Literal::HexOctalBinary(str) => str.to_string(),
|
|
||||||
Literal::Number(str) => str.to_string(),
|
|
||||||
Literal::Record(field_exprs) => {
|
|
||||||
let mut field_strings = Vec::new();
|
|
||||||
|
|
||||||
for (field, subexpr) in field_exprs {
|
|
||||||
let val = literal_to_string(eval(subexpr));
|
|
||||||
|
|
||||||
field_strings.push(format!("{} = {}", field, val));
|
|
||||||
}
|
|
||||||
|
|
||||||
format!("{{ {} }}", field_strings.join(", "))
|
|
||||||
},
|
|
||||||
Literal::Tuple(elem_exprs) => {
|
|
||||||
let mut elem_strings = Vec::new();
|
|
||||||
|
|
||||||
for elem_expr in elem_exprs {
|
|
||||||
elem_strings.push(literal_to_string(eval(elem_expr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
format!("({})", elem_strings.join(", "))
|
|
||||||
},
|
|
||||||
Literal::Array(elem_exprs) => {
|
|
||||||
let mut elem_strings = Vec::new();
|
|
||||||
|
|
||||||
for elem_expr in elem_exprs {
|
|
||||||
elem_strings.push(literal_to_string(eval(elem_expr)));
|
|
||||||
}
|
|
||||||
|
|
||||||
format!("[{}]", elem_strings.join(", "))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
pub type Name = String;
|
|
102
src/parse.rs
102
src/parse.rs
|
@ -2,11 +2,10 @@ use expr::Operator;
|
||||||
use expr::Expr;
|
use expr::Expr;
|
||||||
|
|
||||||
use std::char;
|
use std::char;
|
||||||
use std::iter;
|
|
||||||
|
|
||||||
use combine::parser::char::{char, string, letter, alpha_num, spaces, digit, hex_digit, HexDigit};
|
use combine::parser::char::{char, spaces, digit, hex_digit, HexDigit};
|
||||||
use combine::parser::repeat::{many, count_min_max};
|
use combine::parser::repeat::{many, count_min_max};
|
||||||
use combine::parser::item::{any, satisfy, satisfy_map, value};
|
use combine::parser::item::{any, satisfy_map, value};
|
||||||
use combine::{choice, many1, parser, Parser, optional, between, unexpected_any};
|
use combine::{choice, many1, parser, Parser, optional, between, unexpected_any};
|
||||||
use combine::error::{Consumed, ParseError};
|
use combine::error::{Consumed, ParseError};
|
||||||
use combine::stream::{Stream};
|
use combine::stream::{Stream};
|
||||||
|
@ -39,13 +38,13 @@ parser! {
|
||||||
optional(
|
optional(
|
||||||
operator()
|
operator()
|
||||||
.skip(spaces())
|
.skip(spaces())
|
||||||
.and(expr()
|
.and(expr())
|
||||||
)
|
)
|
||||||
)).map(|(v1, maybe_op)| {
|
).map(|(v1, maybe_op)| {
|
||||||
match maybe_op {
|
match maybe_op {
|
||||||
None => v1,
|
None => v1,
|
||||||
Some((op, v2)) => {
|
Some((op, v2)) => {
|
||||||
Expr::CallOperator(Box::new(v1), op, Box::new(v2))
|
Expr::Operator(Box::new(v1), op, Box::new(v2))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -60,6 +59,7 @@ where I: Stream<Item = char>,
|
||||||
char('+').map(|_| Operator::Plus),
|
char('+').map(|_| Operator::Plus),
|
||||||
char('-').map(|_| Operator::Minus),
|
char('-').map(|_| Operator::Minus),
|
||||||
char('*').map(|_| Operator::Star),
|
char('*').map(|_| Operator::Star),
|
||||||
|
char('/').map(|_| Operator::Slash),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,98 +238,12 @@ where I: Stream<Item = char>,
|
||||||
let numerator = (int_val * denom) + (decimal as i64);
|
let numerator = (int_val * denom) + (decimal as i64);
|
||||||
|
|
||||||
if is_positive {
|
if is_positive {
|
||||||
Expr::Ratio(numerator, denom as u64)
|
Expr::Frac(numerator, denom as u64)
|
||||||
} else {
|
} else {
|
||||||
Expr::Ratio(-numerator, denom as u64)
|
Expr::Frac(-numerator, denom as u64)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub fn parse_expr(state: &mut State) -> Result<Expr, Problem> {
|
|
||||||
|
|
||||||
// let digits = chomp_digits(state);
|
|
||||||
|
|
||||||
// if digits.is_empty() {
|
|
||||||
// Err(Problem::InvalidNumber)
|
|
||||||
// } else {
|
|
||||||
// // TODO store these in a bigint, and handle overflow.
|
|
||||||
// let num = digits.parse::<u32>().unwrap();
|
|
||||||
|
|
||||||
// if decimal_point
|
|
||||||
|
|
||||||
|
|
||||||
// Ok(Expr::Int(num))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// enum Parsed {
|
|
||||||
// Expr(Expr),
|
|
||||||
// Malformed(Problem),
|
|
||||||
// NotFound
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// #[inline]
|
|
||||||
// fn number_parser() -> {
|
|
||||||
// let has_minus_sign = false;
|
|
||||||
// let decimal_point_index: usize = 0;
|
|
||||||
// let len: usize = 0;
|
|
||||||
|
|
||||||
// for ch in state.text.chars() {
|
|
||||||
// if ch.is_ascii_digit() {
|
|
||||||
// len += 1;
|
|
||||||
// } else if ch == '-' {
|
|
||||||
// if has_minus_sign {
|
|
||||||
// if len == 1 {
|
|
||||||
// return Malformed(DoubleMinusSign);
|
|
||||||
// } else {
|
|
||||||
// // This second minus sign is a subtraction operator.
|
|
||||||
// // We've reached the end of the number!
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// has_minus_sign = true;
|
|
||||||
// len += 1;
|
|
||||||
// }
|
|
||||||
// } else if ch == '.' {
|
|
||||||
// if len == 0 {
|
|
||||||
// return Malformed(NoDigitsBeforeDecimalPoint);
|
|
||||||
// } else if decimal_point_index != 0 {
|
|
||||||
// return Malformed(DoubleDecimalPoint);
|
|
||||||
// } else {
|
|
||||||
// // This might be a valid decimal number!
|
|
||||||
// decimal_point_index = len;
|
|
||||||
|
|
||||||
// len += 1;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// state.col += len;
|
|
||||||
|
|
||||||
// if decimal_point_index == 0 {
|
|
||||||
// // This is an integer.
|
|
||||||
// Expr(Expr::Int(parse_int(&state.text[..len])))
|
|
||||||
// } else {
|
|
||||||
// // This is a decimal.
|
|
||||||
// let before_decimal_pt = &state.text[..decimal_point_index];
|
|
||||||
// let after_decimal_pt = &state.text[(decimal_point_index + 1)..];
|
|
||||||
|
|
||||||
// let numerator_str = before_decimal_pt.to_owned();
|
|
||||||
// numerator_str.push_str(after_decimal_pt);
|
|
||||||
|
|
||||||
// let numerator = parse_int(&numerator_str);
|
|
||||||
// let denominator = 10 * after_decimal_pt.len() as u64;
|
|
||||||
|
|
||||||
// Expr(Expr::Ratio(numerator, denominator))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// #[inline]
|
|
||||||
// fn parse_int(text: &str) -> i64 {
|
|
||||||
// // TODO parse as BigInt
|
|
||||||
// text.parse::<i64>().unwrap()
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
63
src/repl.rs
63
src/repl.rs
|
@ -1,63 +0,0 @@
|
||||||
use interpret::{eval, literal_to_string};
|
|
||||||
use unify::infer;
|
|
||||||
use unify::Expr;
|
|
||||||
use unify::Type;
|
|
||||||
|
|
||||||
pub fn eval_and_print<'a>(expr: &Expr<'a>) -> String {
|
|
||||||
match infer(&expr) {
|
|
||||||
Ok(typ) => {
|
|
||||||
let lit = eval(expr);
|
|
||||||
|
|
||||||
format!("{}\n: {}", literal_to_string(lit), type_to_string(true, &typ))
|
|
||||||
},
|
|
||||||
Err(_) =>
|
|
||||||
"[TYPE MISMATCH!]".to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn type_to_string<'a>(outermost: bool, typ: &'a Type<'a>) -> String {
|
|
||||||
match typ {
|
|
||||||
Type::Unbound => "*".to_string(),
|
|
||||||
Type::String => "String".to_string(),
|
|
||||||
Type::Char => "Char".to_string(),
|
|
||||||
Type::Int => "Int".to_string(),
|
|
||||||
Type::Float => "Float".to_string(),
|
|
||||||
Type::Number => "Int | Float".to_string(),
|
|
||||||
Type::Symbol(sym) => format!(":{}", sym),
|
|
||||||
Type::Array(elem_type) => {
|
|
||||||
let str = format!("Array {}", type_to_string(false, elem_type));
|
|
||||||
|
|
||||||
if outermost {
|
|
||||||
str
|
|
||||||
} else {
|
|
||||||
format!("({})", str)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Type::Record(fields) => {
|
|
||||||
let field_strings = fields.into_iter().map(|(field, subtyp)| {
|
|
||||||
let typ_str = type_to_string(false, subtyp);
|
|
||||||
|
|
||||||
format!("{} : {}", field, typ_str)
|
|
||||||
});
|
|
||||||
|
|
||||||
format!("{{ {} }}", field_strings.collect::<Vec<String>>().join(", "))
|
|
||||||
},
|
|
||||||
Type::Tuple(elems) => {
|
|
||||||
let elem_strings = elems.into_iter().map(|subtyp| { type_to_string(false, subtyp) });
|
|
||||||
let str = elem_strings.collect::<Vec<String>>().join(", ");
|
|
||||||
|
|
||||||
if outermost {
|
|
||||||
str
|
|
||||||
} else {
|
|
||||||
format!("({})", str)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Type::Assignment(_, assigned_typ) => type_to_string(outermost, assigned_typ),
|
|
||||||
Type::Union(set) => {
|
|
||||||
set.into_iter().collect::<Vec<&'a Type<'a>>>().into_iter().map(|typ_in_set| {
|
|
||||||
type_to_string(false, typ_in_set)
|
|
||||||
}).collect::<Vec<String>>().join(" | ")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
317
src/solve.rs
317
src/solve.rs
|
@ -1,317 +0,0 @@
|
||||||
use std::collections::BTreeSet;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use constrain::Constraint;
|
|
||||||
use typ::Type;
|
|
||||||
use canonical::Annotation;
|
|
||||||
use name::Name;
|
|
||||||
use self::Variable::*;
|
|
||||||
use ena::unify::{UnificationTable, UnifyKey, InPlace};
|
|
||||||
|
|
||||||
type UTable = UnificationTable<InPlace<VarId>>;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
enum Variable {
|
|
||||||
Wildcard,
|
|
||||||
RigidVar(Name),
|
|
||||||
FlexUnion(BTreeSet<VarId>),
|
|
||||||
RigidUnion(BTreeSet<VarId>),
|
|
||||||
Structure(FlatType),
|
|
||||||
Mismatch
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
enum FlatType {
|
|
||||||
Function(VarId, VarId),
|
|
||||||
|
|
||||||
// Apply a higher-kinded type constructor by name. For example:
|
|
||||||
// "Apply the higher-kinded type constructor `Array` to the variable `Int`
|
|
||||||
// to form `Array Int`."
|
|
||||||
// ApplyTypeConstructor(CanonicalModuleName, Name, VarId)
|
|
||||||
Tuple2(VarId, VarId),
|
|
||||||
Tuple3(VarId, VarId, VarId),
|
|
||||||
// TupleN(Vec<VarId>), // Last resort - allocates
|
|
||||||
// Record1 (Map.Map N.Name VarId) VarId,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn unify_rigid(named: &Variable, other: &Variable) -> Variable {
|
|
||||||
match other {
|
|
||||||
Wildcard => named.clone(),
|
|
||||||
RigidVar(_) => Mismatch,
|
|
||||||
FlexUnion(_) => Mismatch,
|
|
||||||
RigidUnion(_) => Mismatch,
|
|
||||||
Structure(_) => { panic!("TODO"); Mismatch }
|
|
||||||
Mismatch => other.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn unify_rigid_union(utable: &mut UTable, rigid_union: &BTreeSet<VarId>, var: &Variable, other: &Variable) -> Variable {
|
|
||||||
match other {
|
|
||||||
Wildcard => var.clone(),
|
|
||||||
RigidVar(_) => Mismatch,
|
|
||||||
FlexUnion(flex_union) => {
|
|
||||||
if rigid_union_fits_flex_union(utable, &rigid_union, &flex_union) {
|
|
||||||
var.clone()
|
|
||||||
} else {
|
|
||||||
Mismatch
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Structure(_) => { panic!("TODO"); Mismatch }
|
|
||||||
RigidUnion(_) => Mismatch,
|
|
||||||
Mismatch => other.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn rigid_union_fits_flex_union(utable: &mut UTable, rigid_union: &BTreeSet<VarId>, flex_union: &BTreeSet<VarId>) -> bool {
|
|
||||||
if rigid_union.is_subset(&flex_union) {
|
|
||||||
// If the keys of the rigid one are a subset of the flex keys, we're done.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let potentially_missing_flex_ids = flex_union.difference(rigid_union);
|
|
||||||
|
|
||||||
// a flex union can conform to a rigid one, as long
|
|
||||||
// as the rigid union contains all the flex union's alternative types
|
|
||||||
let rigid_union_values: BTreeSet<Variable> =
|
|
||||||
rigid_union.iter().map(|var_id| utable.probe_value(*var_id)).collect();
|
|
||||||
|
|
||||||
for flex_var_id in potentially_missing_flex_ids {
|
|
||||||
let flex_val = utable.probe_value(*flex_var_id);
|
|
||||||
|
|
||||||
if !rigid_union_values.contains(&flex_val) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn unify_flex_union(utable: &mut UTable, flex_union: &BTreeSet<VarId>, var: &Variable, other: &Variable) -> Variable {
|
|
||||||
match other {
|
|
||||||
Wildcard => var.clone(),
|
|
||||||
RigidVar(_) => Mismatch,
|
|
||||||
RigidUnion(rigid_union) => {
|
|
||||||
if rigid_union_fits_flex_union(utable, &rigid_union, &flex_union) {
|
|
||||||
other.clone()
|
|
||||||
} else {
|
|
||||||
Mismatch
|
|
||||||
}
|
|
||||||
},
|
|
||||||
FlexUnion(other_union) => unify_flex_unions(&flex_union, &other_union),
|
|
||||||
Structure(_) => unify_flex_union_with_structure(&flex_union, other),
|
|
||||||
Mismatch => other.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn unify_flex_unions(my_union: &BTreeSet<VarId>, other_union: &BTreeSet<VarId>) -> Variable {
|
|
||||||
let ids_in_common = my_union.intersection(other_union);
|
|
||||||
let unified_union: BTreeSet<VarId> = ids_in_common.into_iter().map(|var_id| *var_id).collect();
|
|
||||||
|
|
||||||
// If they have no types in common, that's a mismatch.
|
|
||||||
if unified_union.len() == 0 {
|
|
||||||
Mismatch
|
|
||||||
} else {
|
|
||||||
FlexUnion(unified_union)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unify_vars(utable: &mut UTable, first: &Variable, second: &Variable) -> Variable {
|
|
||||||
match first {
|
|
||||||
// wildcard types defer to whatever the other type happens to be.
|
|
||||||
Wildcard => second.clone(),
|
|
||||||
FlexUnion(union) => unify_flex_union(utable, &union, first, second),
|
|
||||||
RigidVar(Name) => unify_rigid(first, second),
|
|
||||||
RigidUnion(union) => unify_rigid_union(utable, &union, first, second),
|
|
||||||
Structure(flat_type) => unify_structure(utable, flat_type, first, second),
|
|
||||||
// Mismatches propagate.
|
|
||||||
Mismatch => first.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn unify_structure(utable: &mut UTable, flat_type: &FlatType, var: &Variable, other: &Variable) -> Variable {
|
|
||||||
match other {
|
|
||||||
Wildcard => var.clone(),
|
|
||||||
RigidVar(_) => Mismatch,
|
|
||||||
FlexUnion(flex_union) => unify_flex_union_with_structure(&flex_union, var),
|
|
||||||
RigidUnion(_) => Mismatch,
|
|
||||||
Structure(other_flat_type) => unify_flat_types(utable, flat_type, other_flat_type),
|
|
||||||
Mismatch => other.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn unify_flat_types(utable: &mut UTable, flat_type: &FlatType, other_flat_type: &FlatType) -> Variable {
|
|
||||||
match (flat_type, other_flat_type) {
|
|
||||||
(FlatType::Function(my_arg, my_return),
|
|
||||||
FlatType::Function(other_arg, other_return)) => {
|
|
||||||
let new_arg = unify_var_ids(utable, *my_arg, *other_arg);
|
|
||||||
let new_return = unify_var_ids(utable, *my_return, *other_return);
|
|
||||||
|
|
||||||
// Propagate any mismatches.
|
|
||||||
if new_arg == Mismatch {
|
|
||||||
new_arg
|
|
||||||
} else if new_return == Mismatch {
|
|
||||||
new_return
|
|
||||||
} else {
|
|
||||||
let new_arg_id = utable.new_key(new_arg);
|
|
||||||
let new_return_id = utable.new_key(new_return);
|
|
||||||
|
|
||||||
Structure(FlatType::Function(new_arg_id, new_return_id))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(FlatType::Function(_, __return), _) => Mismatch,
|
|
||||||
(_, FlatType::Function(_, __return)) => Mismatch,
|
|
||||||
(FlatType::Tuple2(my_first, my_second),
|
|
||||||
FlatType::Tuple2(other_first, other_second)) => {
|
|
||||||
let new_first = unify_var_ids(utable, *my_first, *other_first);
|
|
||||||
let new_second = unify_var_ids(utable, *my_second, *other_second);
|
|
||||||
|
|
||||||
// Propagate any mismatches.
|
|
||||||
if new_first == Mismatch {
|
|
||||||
new_first
|
|
||||||
} else if new_second == Mismatch {
|
|
||||||
new_second
|
|
||||||
} else {
|
|
||||||
let new_first_id = utable.new_key(new_first);
|
|
||||||
let new_second_id = utable.new_key(new_second);
|
|
||||||
|
|
||||||
Structure(FlatType::Tuple2(new_first_id, new_second_id))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
(FlatType::Tuple2(_, _), _) => Mismatch,
|
|
||||||
(_, FlatType::Tuple2(_, _)) => Mismatch,
|
|
||||||
(FlatType::Tuple3(my_first, my_second, my_third),
|
|
||||||
FlatType::Tuple3(other_first, other_second, other_third)) => {
|
|
||||||
let new_first = unify_var_ids(utable, *my_first, *other_first);
|
|
||||||
let new_second = unify_var_ids(utable, *my_second, *other_second);
|
|
||||||
let new_third = unify_var_ids(utable, *my_third, *other_third);
|
|
||||||
|
|
||||||
// Propagate any mismatches.
|
|
||||||
if new_first == Mismatch {
|
|
||||||
new_first
|
|
||||||
} else if new_second == Mismatch {
|
|
||||||
new_second
|
|
||||||
} else if new_third == Mismatch {
|
|
||||||
new_third
|
|
||||||
} else {
|
|
||||||
let new_first_id = utable.new_key(new_first);
|
|
||||||
let new_second_id = utable.new_key(new_second);
|
|
||||||
let new_third_id = utable.new_key(new_third);
|
|
||||||
|
|
||||||
Structure(FlatType::Tuple3(new_first_id, new_second_id, new_third_id))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
// (FlatType::Tuple3(_, _, _), _) => Mismatch,
|
|
||||||
// (_, FlatType::Tuple3(_, _, _)) => Mismatch,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn unify_flex_union_with_structure(flex_union: &BTreeSet<VarId>, var: &Variable) -> Variable {
|
|
||||||
// TODO I guess iterate through the set, looking up Variables
|
|
||||||
|
|
||||||
panic!("TODO");
|
|
||||||
// if flex_union.contains(var) {
|
|
||||||
// Narrow the union to the one member type
|
|
||||||
var.clone()
|
|
||||||
// } else {
|
|
||||||
// Mismatch
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Given a type, create a constraint variable for it and add it to the table.
|
|
||||||
// Return the VarId corresponding to the variable in the table.
|
|
||||||
fn type_to_var_id(utable: &mut UTable, typ: Type) -> VarId {
|
|
||||||
match typ {
|
|
||||||
Type::Call(box fn_type, box arg_type) => {
|
|
||||||
panic!("TODO");
|
|
||||||
utable.new_key(Mismatch)
|
|
||||||
// let left_var_id = type_to_var_id(utable, left_type);
|
|
||||||
// let right_var_id = type_to_var_id(utable, right_type);
|
|
||||||
|
|
||||||
// // TODO should we match on op to hardcode the types we expect?
|
|
||||||
// let flat_type = FlatType::Function(left_var_id, right_var_id);
|
|
||||||
|
|
||||||
// utable.new_key(Structure(flat_type))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
struct VarId(u32);
|
|
||||||
|
|
||||||
impl UnifyKey for VarId {
|
|
||||||
type Value = Variable;
|
|
||||||
|
|
||||||
fn index(&self) -> u32 { self.0 }
|
|
||||||
fn from_index(u: u32) -> VarId { VarId(u) }
|
|
||||||
|
|
||||||
// tag is a static string that's only used in debugging
|
|
||||||
fn tag() -> &'static str { "VarId" }
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unify_var_ids(utable: &mut UTable, left_id: VarId, right_id: VarId) -> Variable {
|
|
||||||
let left_content = utable.probe_value(left_id);
|
|
||||||
let right_content = utable.probe_value(right_id);
|
|
||||||
|
|
||||||
if left_content == right_content {
|
|
||||||
left_content
|
|
||||||
} else {
|
|
||||||
unify_vars(utable, &left_content, &right_content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type TypeError = String;
|
|
||||||
|
|
||||||
pub fn solve_constraint(constraint: Constraint) -> Result<TypeError, HashMap<Name, Annotation>> {
|
|
||||||
let mut utable: UTable = UnificationTable::new();
|
|
||||||
|
|
||||||
solve(&mut utable, constraint);
|
|
||||||
|
|
||||||
Ok("TODO: actually gather errors etc".to_owned())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn solve(utable: &mut UTable, constraint: Constraint) {
|
|
||||||
match constraint {
|
|
||||||
Constraint::True => {},
|
|
||||||
|
|
||||||
Constraint::Equal(actual_type, expectation) => {
|
|
||||||
let actual_var_id = type_to_var_id(utable, actual_type);
|
|
||||||
let expected_var_id = type_to_var_id(utable, expectation);
|
|
||||||
let answer = unify_var_ids(utable, actual_var_id, expected_var_id);
|
|
||||||
|
|
||||||
panic!("Oh no! TYPE MISMATCH! (TODO: record errors as appropriate)");
|
|
||||||
()
|
|
||||||
// match answer {
|
|
||||||
// Mismatch => {
|
|
||||||
// panic!("Oh no! TYPE MISMATCH! (TODO: record errors as appropriate)");
|
|
||||||
// }
|
|
||||||
// do introduce rank pools vars
|
|
||||||
// return state
|
|
||||||
|
|
||||||
// UF.modify var $ \(Descriptor content _ mark copy) ->
|
|
||||||
// Descriptor content rank mark copy
|
|
||||||
|
|
||||||
// Unify.Err vars actualType expectedType ->
|
|
||||||
|
|
||||||
// panic!("TODO xyz");
|
|
||||||
// do introduce rank pools vars
|
|
||||||
// return $ addError state $
|
|
||||||
// Error.BadExpr region category actualType $
|
|
||||||
// Error.typeReplace expectation expectedType
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
|
|
||||||
Constraint::Batch(_) => {
|
|
||||||
panic!("TODO");
|
|
||||||
()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
12
src/typ.rs
12
src/typ.rs
|
@ -1,12 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub enum Type {
|
|
||||||
// Symbol(String),
|
|
||||||
// Int,
|
|
||||||
// Float,
|
|
||||||
// Number,
|
|
||||||
// TypeUnion(BTreeSet<Type>),
|
|
||||||
// Function(Box<Type>, Box<Type>),
|
|
||||||
Call(Box<Type>, Box<Type>),
|
|
||||||
}
|
|
363
src/unify.rs
363
src/unify.rs
|
@ -1,363 +0,0 @@
|
||||||
use std::collections::BTreeSet;
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use self::Type::*;
|
|
||||||
|
|
||||||
pub type Name<'a> = &'a str;
|
|
||||||
|
|
||||||
pub type ModuleName<'a> = &'a str;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
||||||
pub enum Type<'a> {
|
|
||||||
Unbound,
|
|
||||||
String,
|
|
||||||
Char,
|
|
||||||
Int,
|
|
||||||
Float,
|
|
||||||
Number,
|
|
||||||
Symbol(&'a str),
|
|
||||||
Array(Box<Type<'a>>),
|
|
||||||
Function(Box<Type<'a>>, Box<Type<'a>>),
|
|
||||||
Record(BTreeMap<Name<'a>, Type<'a>>),
|
|
||||||
Tuple(Vec<Type<'a>>),
|
|
||||||
Union(BTreeSet<Type<'a>>),
|
|
||||||
}
|
|
||||||
|
|
||||||
// CANONICAL IR - we have already done stuff like giving errors for
|
|
||||||
// duplicate field names
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Expr<'a> {
|
|
||||||
// Variables
|
|
||||||
Declaration(&'a Pattern<'a>, Box<&'a Expr<'a>>, Box<Expr<'a>>),
|
|
||||||
LookupLocal(&'a Name<'a>),
|
|
||||||
LookupGlobal(&'a ModuleName<'a>, &'a Name<'a>),
|
|
||||||
|
|
||||||
// Scalars
|
|
||||||
Symbol(&'a str),
|
|
||||||
String(&'a str),
|
|
||||||
Char(char),
|
|
||||||
HexOctalBinary(i64), // : Int
|
|
||||||
FractionalNumber(f64), // : Float
|
|
||||||
WholeNumber(i64), // : Int | Float
|
|
||||||
|
|
||||||
// Collections
|
|
||||||
Array(Vec<Expr<'a>>),
|
|
||||||
Record(Vec<(&'a Name<'a>, &'a Expr<'a>)>),
|
|
||||||
Tuple(Vec<&'a Expr<'a>>),
|
|
||||||
LookupName(Name<'a>, Box<&'a Expr<'a>>),
|
|
||||||
// TODO add record update
|
|
||||||
|
|
||||||
// Functions
|
|
||||||
Function(&'a Pattern<'a>, &'a Expr<'a>),
|
|
||||||
Call(Box<&'a Expr<'a>>, Box<&'a Expr<'a>>),
|
|
||||||
CallOperator(&'a Operator, Box<&'a Expr<'a>>, Box<&'a Expr<'a>>),
|
|
||||||
|
|
||||||
// Conditionals
|
|
||||||
If(Box<&'a Expr<'a>> /* Conditional */, Box<&'a Expr<'a>> /* True branch */, Box<&'a Expr<'a>> /* False branch */),
|
|
||||||
Case(Box<&'a Expr<'a>>, Vec<(&'a Pattern<'a>, &'a Expr<'a>)>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Operator {
|
|
||||||
Plus, Minus, Star, Caret, Percent, FloatDivision, IntDivision,
|
|
||||||
GT, GTE, LT, LTE,
|
|
||||||
EQ, NE, And, Or,
|
|
||||||
QuestionMark, Or
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq)]
|
|
||||||
pub enum Pattern<'a> {
|
|
||||||
Name(&'a Name<'a>), // `foo =`
|
|
||||||
As(&'a Name<'a>, &'a Pattern<'a>), // `<pattern> as foo`
|
|
||||||
Type(&'a Type<'a>),
|
|
||||||
Symbol(&'a str),
|
|
||||||
String(&'a str),
|
|
||||||
Char(char),
|
|
||||||
WholeNumber(&'a str),
|
|
||||||
FractionalNumber(&'a str),
|
|
||||||
HexOctalBinary(&'a str),
|
|
||||||
Tuple(Vec<Pattern<'a>>),
|
|
||||||
Record(Vec<(Name<'a>, Option<Pattern<'a>>)>), // { a = 5, b : Int as x, c }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub fn infer<'a>(expr: &Expr<'a>) -> Result<Type<'a>, UnificationProblem> {
|
|
||||||
match expr {
|
|
||||||
Expr::String(_) => Ok(String),
|
|
||||||
Expr::Char(_) => Ok(Char),
|
|
||||||
Expr::HexOctalBinary(_) => Ok(Int),
|
|
||||||
Expr::FractionalNumber(_) => Ok(Float),
|
|
||||||
Expr::WholeNumber(_) => Ok(Number),
|
|
||||||
Expr::Symbol(sym) => Ok(Symbol(sym)),
|
|
||||||
Expr::Array(elem_exprs) => {
|
|
||||||
let elem_type;
|
|
||||||
|
|
||||||
if elem_exprs.is_empty() {
|
|
||||||
elem_type = Unbound;
|
|
||||||
} else {
|
|
||||||
let mut unified_type = BTreeSet::new();
|
|
||||||
|
|
||||||
// Unify the types of all the elements
|
|
||||||
for elem_expr in elem_exprs {
|
|
||||||
unified_type.insert(infer(&elem_expr)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
if unified_type.len() == 1 {
|
|
||||||
// No point in storing a union of 1.
|
|
||||||
elem_type = unified_type.into_iter().next().unwrap()
|
|
||||||
} else {
|
|
||||||
elem_type = Union(unified_type)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Array(Box::new(elem_type)))
|
|
||||||
},
|
|
||||||
Expr::Record(fields) => {
|
|
||||||
let mut rec_type: BTreeMap<&'a Name<'a>, Type<'a>> = BTreeMap::new();
|
|
||||||
|
|
||||||
for (field, subexpr) in fields {
|
|
||||||
let field_type = infer(subexpr)?;
|
|
||||||
|
|
||||||
rec_type.insert(&field, field_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Record(rec_type))
|
|
||||||
},
|
|
||||||
Expr::Tuple(exprs) => {
|
|
||||||
let mut tuple_type: Vec<Type<'a>> = Vec::new();
|
|
||||||
|
|
||||||
for subexpr in exprs {
|
|
||||||
let field_type = infer(subexpr)?;
|
|
||||||
|
|
||||||
tuple_type.push(field_type);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Tuple(tuple_type))
|
|
||||||
},
|
|
||||||
Expr::If(box cond, expr_if_true, expr_if_false) => {
|
|
||||||
let cond_type = infer(&cond)?;
|
|
||||||
|
|
||||||
// if-conditionals must be of type Bool
|
|
||||||
if !matches_bool_type(&cond_type) {
|
|
||||||
return Err(UnificationProblem::IfConditionNotBool);
|
|
||||||
}
|
|
||||||
|
|
||||||
// unify the true and false branches
|
|
||||||
let true_type = infer(&expr_if_true)?;
|
|
||||||
let false_type = infer(&expr_if_false)?;
|
|
||||||
|
|
||||||
let mut unified_type = BTreeSet::new();
|
|
||||||
|
|
||||||
unified_type.insert(true_type);
|
|
||||||
unified_type.insert(false_type);
|
|
||||||
|
|
||||||
if unified_type.len() == 1 {
|
|
||||||
// No point in storing a union of 1.
|
|
||||||
//
|
|
||||||
// We can't reuse true_type because it's been moved into the set
|
|
||||||
// but we can pull it back out of the set
|
|
||||||
Ok(unified_type.into_iter().next().unwrap())
|
|
||||||
} else {
|
|
||||||
Ok(Union(unified_type))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Call(func, arg) => {
|
|
||||||
|
|
||||||
},
|
|
||||||
CallOperator(op, left_expr, right_expr) => {
|
|
||||||
let left = &(infer(left_expr)?);
|
|
||||||
let right = &(infer(right_expr)?);
|
|
||||||
|
|
||||||
match op {
|
|
||||||
Operator::EQ | Operator::NE | Operator::And | Operator::Or => {
|
|
||||||
if types_match(left, right) {
|
|
||||||
conform_to_bool(left)
|
|
||||||
} else {
|
|
||||||
Err(UnificationProblem::TypeMismatch)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Operator::Plus | Operator::Minus | Operator::Star
|
|
||||||
| Operator::GT | Operator::LT | Operator::GTE | Operator::LTE
|
|
||||||
| Operator::Caret | Operator::Percent => {
|
|
||||||
if types_match(left, right) {
|
|
||||||
conform_to_number(left)
|
|
||||||
} else {
|
|
||||||
Err(UnificationProblem::TypeMismatch)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Operator::FloatDivision => {
|
|
||||||
if matches_float_type(left) && matches_float_type(right) {
|
|
||||||
Ok(&Float)
|
|
||||||
} else {
|
|
||||||
Err(UnificationProblem::TypeMismatch)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Operator::IntDivision => {
|
|
||||||
if matches_int_type(left) && matches_int_type(right) {
|
|
||||||
Ok(&Int)
|
|
||||||
} else {
|
|
||||||
Err(UnificationProblem::TypeMismatch)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Operator::CombineStrings => {
|
|
||||||
if matches_string_type(left) && matches_string_type(right) {
|
|
||||||
Ok(&String)
|
|
||||||
} else {
|
|
||||||
Err(UnificationProblem::TypeMismatch)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Operator::QuestionMark => {
|
|
||||||
if types_match(left, right) {
|
|
||||||
conform_to_optional(left)
|
|
||||||
} else {
|
|
||||||
Err(UnificationProblem::TypeMismatch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Expr::Declaration(pattern, let_expr, in_expr) => {
|
|
||||||
// Think of this as a let..in even though syntactically it's not.
|
|
||||||
// We need to type-check the let-binding, but the type of the
|
|
||||||
// *expression* we're expaning is only affected by the in-block.
|
|
||||||
check_pattern(&pattern, &let_expr)?;
|
|
||||||
|
|
||||||
infer(in_expr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn types_match<'a>(first: &'a Type<'a>, second: &'a Type<'a>) -> bool {
|
|
||||||
match (first, second) {
|
|
||||||
(Type::Union(first_types), Type::Union(second_types)) => {
|
|
||||||
// If any type is not directly present in the other union,
|
|
||||||
// it must at least match *some* type in the other union
|
|
||||||
first_types.difference(second_types).into_iter().all(|not_in_second_type| {
|
|
||||||
second_types.iter().any(|second_type| types_match(second_type, not_in_second_type))
|
|
||||||
}) &&
|
|
||||||
second_types.difference(first_types).into_iter().all(|not_in_first_type| {
|
|
||||||
first_types.iter().any(|first_type| types_match(first_type, not_in_first_type))
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
// Happy path: try these first, since we expect them to succeed.
|
|
||||||
// These are sorted based on a vague guess of how often they will be used in practice.
|
|
||||||
(Type::Symbol(sym_one), Type::Symbol(sym_two)) => sym_one == sym_two,
|
|
||||||
(Type::String, Type::String) => true,
|
|
||||||
(Type::Unbound, _) | (_, Type::Unbound)=> true,
|
|
||||||
(Type::Array(box elem_type_one), Type::Array(box elem_type_two)) => {
|
|
||||||
types_match(elem_type_one, elem_type_two)
|
|
||||||
},
|
|
||||||
(Type::Number, Type::Number) => true,
|
|
||||||
(Type::Number, other) => matches_number_type(other),
|
|
||||||
(other, Type::Number) => matches_number_type(other),
|
|
||||||
(Type::Int, Type::Int) => true,
|
|
||||||
(Type::Float, Type::Float) => true,
|
|
||||||
(Type::Tuple(first_elems), Type::Tuple(second_elems)) => {
|
|
||||||
// TODO verify that the elems and their types match up
|
|
||||||
// TODO write some scenarios to understand these better -
|
|
||||||
// like, what happens if you have a function that takes
|
|
||||||
// a lambda whose argument takes an open record,
|
|
||||||
// and you pass a lamba whose argument takes *fewer* fields?
|
|
||||||
// that should work! the function is gonna pass it a lambda that
|
|
||||||
// has more fields than it needs.
|
|
||||||
// I think there's an element of directionality here that I'm
|
|
||||||
// disregarding. Maybe this function shouldn't commute.
|
|
||||||
},
|
|
||||||
(Type::Function(first_arg), Type::Function(second_arg)) => {
|
|
||||||
// TODO verify that the elems and their types match up
|
|
||||||
},
|
|
||||||
(Type::Record(first_fields), Type::Record(second_fields)) => {
|
|
||||||
// TODO verify that the fields and their types match up
|
|
||||||
// TODO what should happen if one is a superset of the other? fail?
|
|
||||||
},
|
|
||||||
(Type::Char, Type::Char) => true,
|
|
||||||
|
|
||||||
// Unhappy path - expect these to fail, so check them last
|
|
||||||
(Type::Union(first_types), _) => {
|
|
||||||
first_types.iter().all(|typ| types_match(typ, second))
|
|
||||||
},
|
|
||||||
(_, Type::Union(second_types)) => {
|
|
||||||
second_types.iter().all(|typ| types_match(first, typ))
|
|
||||||
},
|
|
||||||
(Type::String, _) | (_, Type::String) => false,
|
|
||||||
(Type::Char, _) | (_, Type::Char) => false,
|
|
||||||
(Type::Int, _) | (_, Type::Int) => false,
|
|
||||||
(Type::Float, _) | (_, Type::Float) => false,
|
|
||||||
(Type::Symbol(_), _) | (_, Type::Symbol(_)) => false,
|
|
||||||
(Type::Array(_), _) | (_, Type::Array(_)) => false,
|
|
||||||
(Type::Record(_), _) | (_, Type::Record(_)) => false,
|
|
||||||
(Type::Tuple(_), _) | (_, Type::Tuple(_)) => false,
|
|
||||||
(Type::Function(_, _), _) | (_, Type::Function(_, _)) => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn check_pattern<'a>(pattern: &'a Pattern<'a>, expr: &'a Expr<'a>) -> Result<(), UnificationProblem> {
|
|
||||||
let expr_type = infer(expr)?;
|
|
||||||
|
|
||||||
panic!("TODO check the pattern's type against expr_type, then write some tests for funky record pattern cases - this is our first real unification! Next one will be field access, ooooo - gonna want lots of tests for that")
|
|
||||||
}
|
|
||||||
|
|
||||||
const TRUE_SYMBOL_STR: &'static str = "True";
|
|
||||||
const FALSE_SYMBOL_STR: &'static str = "False";
|
|
||||||
|
|
||||||
pub fn matches_string_type<'a>(candidate: &Type<'a>) -> bool {
|
|
||||||
match candidate {
|
|
||||||
Unbound | String => true,
|
|
||||||
Type::Union(types) => {
|
|
||||||
types.iter().all(|typ| matches_string_type(typ))
|
|
||||||
},
|
|
||||||
_ => Err(UnificationProblem::TypeMismatch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn matches_bool_type<'a>(candidate: &Type<'a>) -> bool {
|
|
||||||
match candidate {
|
|
||||||
Type::Unbound => true,
|
|
||||||
Type::Symbol(str) => str == &TRUE_SYMBOL_STR || str == &FALSE_SYMBOL_STR,
|
|
||||||
Type::Union(types) => {
|
|
||||||
types.iter().all(|typ| matches_bool_type(typ))
|
|
||||||
}
|
|
||||||
_ => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn matches_number_type<'a>(candidate: &Type<'a>) -> bool {
|
|
||||||
match candidate {
|
|
||||||
Type::Unbound | Type::Int | Type::Float | Type::Number => true,
|
|
||||||
Type::Union(types) => {
|
|
||||||
types.iter().all(|typ| matches_number_type(typ))
|
|
||||||
}
|
|
||||||
_ => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn matches_int_type<'a>(candidate: &Type<'a>) -> bool {
|
|
||||||
match candidate {
|
|
||||||
Type::Unbound | Type::Int => true,
|
|
||||||
Type::Union(types) => {
|
|
||||||
types.iter().all(|typ| matches_int_type(typ))
|
|
||||||
}
|
|
||||||
_ => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn matches_float_type<'a>(candidate: &Type<'a>) -> bool {
|
|
||||||
match candidate {
|
|
||||||
Type::Unbound | Type::Float => true,
|
|
||||||
Type::Union(types) => {
|
|
||||||
types.iter().all(|typ| matches_float_type(typ))
|
|
||||||
}
|
|
||||||
_ => false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub enum UnificationProblem {
|
|
||||||
CannotUnifyAssignments,
|
|
||||||
NotMemberOfUnion,
|
|
||||||
TypeMismatch,
|
|
||||||
IfConditionNotBool,
|
|
||||||
SymbolMismatch
|
|
||||||
}
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue