isla_cat/
cat.rs

1// BSD 2-Clause License
2//
3// Copyright (c) 2019, 2020 Alasdair Armstrong
4//
5// All rights reserved.
6//
7// Redistribution and use in source and binary forms, with or without
8// modification, are permitted provided that the following conditions are
9// met:
10//
11// 1. Redistributions of source code must retain the above copyright
12// notice, this list of conditions and the following disclaimer.
13//
14// 2. Redistributions in binary form must reproduce the above copyright
15// notice, this list of conditions and the following disclaimer in the
16// documentation and/or other materials provided with the distribution.
17//
18// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30//! Provides the top-level interface for interacting with cats. 🐱
31
32use std::collections::{HashMap, HashSet};
33use std::env;
34use std::error;
35use std::fmt;
36use std::fs::File;
37use std::io::prelude::*;
38use std::path::{Path, PathBuf};
39
40use crate::cat_lexer;
41use crate::cat_parser;
42
43#[derive(Debug)]
44pub enum Exp<T> {
45    Empty(T),
46    Id(String, T),
47    Let(String, Box<Exp<T>>, Box<Exp<T>>, T),
48    TryWith(Box<Exp<T>>, Box<Exp<T>>, T),
49    /// Set or relation union R | S
50    Union(Box<Exp<T>>, Box<Exp<T>>, T),
51    /// Set or relation intersection R & S
52    Inter(Box<Exp<T>>, Box<Exp<T>>, T),
53    /// Set or relation difference R \ S
54    Diff(Box<Exp<T>>, Box<Exp<T>>, T),
55    /// Relation composition R; S
56    Seq(Box<Exp<T>>, Box<Exp<T>>),
57    /// Cartesian product of sets R * S
58    Cartesian(Box<Exp<T>>, Box<Exp<T>>),
59    /// Set or relation complement ~R
60    Compl(Box<Exp<T>>, T),
61    /// \[R\] Lift a set to the identity relation over its elements
62    Identity(Box<Exp<T>>),
63    /// R? intersect a relation R with the identity relation
64    IdentityUnion(Box<Exp<T>>),
65    /// Inverse of a relation R^-1
66    Inverse(Box<Exp<T>>),
67    /// Function application f(x)
68    App(String, Box<Exp<T>>, T),
69}
70
71/// Cat allows arbitrary variable shadowing, so we have to deal with
72/// it sadly.
73#[derive(Debug, Default)]
74pub struct Shadows {
75    map: HashMap<String, usize>,
76}
77
78impl Shadows {
79    pub fn new() -> Self {
80        Default::default()
81    }
82}
83
84impl<T> Exp<T> {
85    fn unshadow(&mut self, shadows: &mut Shadows, locals: &mut HashMap<String, usize>) {
86        use Exp::*;
87
88        match self {
89            Id(id, _) => {
90                if let Some(count) = locals.get(id) {
91                    *id = format!("{}/l{}", id, count)
92                } else if let Some(count) = shadows.map.get(id) {
93                    *id = format!("{}/{}", id, count)
94                }
95            }
96
97            Let(id, exp1, exp2, _) => {
98                exp1.unshadow(shadows, locals);
99                if let Some(count) = locals.get_mut(id) {
100                    *count += 1;
101                    *id = format!("{}/l{}", id, count)
102                } else {
103                    locals.insert(id.clone(), 0);
104                    *id = format!("{}/l0", id)
105                }
106                exp2.unshadow(shadows, locals)
107            }
108
109            Empty(_) => (),
110            Inverse(exp) | IdentityUnion(exp) | Identity(exp) | Compl(exp, _) | App(_, exp, _) => {
111                exp.unshadow(shadows, locals)
112            }
113            TryWith(exp1, exp2, _)
114            | Union(exp1, exp2, _)
115            | Inter(exp1, exp2, _)
116            | Diff(exp1, exp2, _)
117            | Seq(exp1, exp2)
118            | Cartesian(exp1, exp2) => {
119                exp1.unshadow(shadows, locals);
120                exp2.unshadow(shadows, locals)
121            }
122        }
123    }
124}
125
126#[derive(Debug)]
127pub enum Check {
128    Acyclic,
129    Irreflexive,
130    Empty,
131    NonAcyclic,
132    NonIrreflexive,
133    NonEmpty,
134}
135
136#[derive(Debug)]
137pub enum Def<T> {
138    Let(Vec<(String, Exp<T>)>),
139    Fn(String, Vec<(String, T)>, Exp<T>),
140    TClosure(String, Exp<T>),
141    RTClosure(String, Exp<T>),
142    Flag(Check, Exp<T>, String),
143    Check(Check, Exp<T>, Option<String>),
144    Show(Vec<String>),
145    ShowAs(Exp<T>, String),
146    Unshow(Vec<String>),
147    Set(String),
148    Relation(String),
149}
150
151#[derive(Debug)]
152pub enum ParseDef {
153    Def(Def<()>),
154    Include(String),
155}
156
157/// A `ParseCat` represents a single parsed cat file.
158#[derive(Debug)]
159pub struct ParseCat {
160    pub tag: String,
161    pub defs: Vec<ParseDef>,
162}
163
164impl ParseCat {
165    pub fn from_string(contents: &str) -> Result<Self, String> {
166        let lexer = cat_lexer::Lexer::new(&contents);
167        match cat_parser::CatParser::new().parse(lexer) {
168            Ok(cat) => Ok(cat),
169            Err(e) => Err(format!("Failed to parse cat file: {}", e)),
170        }
171    }
172
173    pub fn from_file<P>(path: P) -> Result<Self, String>
174    where
175        P: AsRef<Path>,
176    {
177        let file_name = path.as_ref().display();
178        let mut contents = String::new();
179
180        match File::open(&path) {
181            Ok(mut handle) => match handle.read_to_string(&mut contents) {
182                Ok(_) => (),
183                Err(e) => return Err(format!("Error when reading cat file '{}': {}", file_name, e)),
184            },
185            Err(e) => return Err(format!("Error when opening cat file '{}': {}", file_name, e)),
186        }
187
188        Self::from_string(&contents)
189    }
190}
191
192/// A `Cat` is a full cat memory-model definition, with all its
193/// includes resolved.
194#[derive(Debug)]
195pub struct Cat<T> {
196    pub tag: String,
197    pub defs: Vec<Def<T>>,
198}
199
200impl<T> Cat<T> {
201    /// Remove all variable shadowing
202    pub fn unshadow(&mut self, shadows: &mut Shadows) {
203        use Def::*;
204
205        for def in self.defs.iter_mut().rev() {
206            match def {
207                Let(bindings) => {
208                    for (id, exp) in bindings.iter_mut() {
209                        exp.unshadow(shadows, &mut HashMap::new());
210                        if let Some(count) = shadows.map.get_mut(id) {
211                            *id = format!("{}/{}", id, count);
212                            *count += 1
213                        } else {
214                            shadows.map.insert(id.clone(), 0);
215                        }
216                    }
217                }
218
219                TClosure(id, exp) | RTClosure(id, exp) => {
220                    exp.unshadow(shadows, &mut HashMap::new());
221                    if let Some(count) = shadows.map.get_mut(id) {
222                        *id = format!("{}/{}", id, count);
223                        *count += 1
224                    } else {
225                        shadows.map.insert(id.clone(), 0);
226                    }
227                }
228
229                Fn(_, _, exp) | Flag(_, exp, _) | Check(_, exp, _) | ShowAs(exp, _) => {
230                    exp.unshadow(shadows, &mut HashMap::new())
231                }
232
233                _ => (),
234            }
235        }
236    }
237
238    pub fn shows(&self) -> Vec<String> {
239        let mut shows = Vec::new();
240        for def in self.defs.iter() {
241            match def {
242                Def::Show(ids) => shows.append(&mut ids.clone()),
243                Def::ShowAs(_, id) => shows.push(id.clone()),
244                _ => (),
245            }
246        }
247        shows
248    }
249}
250
251impl Cat<Ty> {
252    /// Returns the names of all the relations defined by a cat file
253    pub fn relations<'a>(&'a self) -> Vec<&'a str> {
254        let mut rels: Vec<&'a str> = Vec::new();
255        for def in self.defs.iter() {
256            match def {
257                Def::Let(bindings) => bindings.iter().for_each(|(id, exp)| {
258                    if ty_of(exp) == Ty::Rel {
259                        rels.push(id)
260                    }
261                }),
262                Def::TClosure(id, _) | Def::RTClosure(id, _) => rels.push(id),
263                _ => (),
264            }
265        }
266        rels
267    }
268}
269
270/// Turn a `ParseCat` into an full untyped Cat by resolving any
271/// include statements. Note that some included cats are very special,
272/// like `cos.cat` and `stdlib.cat` which are defined internally.
273pub fn resolve_includes(cat_dirs: &[PathBuf], mut parse_cat: ParseCat) -> Result<Cat<()>, String> {
274    Ok(Cat {
275        tag: parse_cat.tag,
276        defs: parse_cat
277            .defs
278            .drain(..)
279            .map(|parse_def| match parse_def {
280                ParseDef::Def(def) => Ok(vec![def]),
281                ParseDef::Include(name) => find_cat(cat_dirs, &name).map(|cat| cat.defs),
282            })
283            .collect::<Result<Vec<_>, _>>()?
284            .drain(..)
285            .flatten()
286            .collect(),
287    })
288}
289
290static COS_CAT: &str = include_str!("../catlib/cos.cat");
291static STDLIB_CAT: &str = include_str!("../catlib/stdlib.cat");
292
293fn find_cat(cat_dirs: &[PathBuf], name: &str) -> Result<Cat<()>, String> {
294    if name == "cos.cat" {
295        let parse_cat = ParseCat::from_string(COS_CAT)?;
296        return resolve_includes(cat_dirs, parse_cat);
297    }
298
299    if name == "stdlib.cat" {
300        let parse_cat = ParseCat::from_string(STDLIB_CAT)?;
301        return resolve_includes(cat_dirs, parse_cat);
302    }
303
304    for dir in cat_dirs {
305        let cat_file = dir.join(name);
306        if cat_file.is_file() {
307            let parse_cat = ParseCat::from_file(cat_file)?;
308            return resolve_includes(cat_dirs, parse_cat);
309        }
310    }
311
312    Err(format!("Could not find cat: {}", name))
313}
314
315/// Load a cat model. The input can either be a path to the cat model such as
316/// `my/favourite/cats/russian_blue.cat`, or the name of a cat like `british_shorthair.cat`. In the
317/// first case any cats included by `russian_blue.cat` will be searched for first in
318/// `my/favourite/cats/` followed by the HERDLIB environment variable (if set). In the second case
319/// they will just be searched for in HERDLIB.
320pub fn load_cat(name: &str) -> Result<Cat<()>, String> {
321    let path = Path::new(name);
322
323    let mut cat_dirs: Vec<PathBuf> = Vec::new();
324
325    if let Ok(directory) = env::var("HERDLIB") {
326        cat_dirs.push(directory.into())
327    }
328
329    let mut directory = path.to_path_buf();
330    directory.pop();
331    if directory.is_dir() {
332        cat_dirs.push(directory)
333    }
334
335    if path.is_file() {
336        let parse_cat = ParseCat::from_file(path)?;
337        resolve_includes(&cat_dirs, parse_cat)
338    } else {
339        find_cat(&cat_dirs, name)
340    }
341}
342
343#[derive(Copy, Clone, Debug, PartialEq, Eq)]
344pub enum Ty {
345    Rel,
346    Set,
347}
348
349/// A type-checking context. For cats.
350pub struct Tcx {
351    bindings: HashMap<String, Vec<Ty>>,
352    functions: HashMap<String, (Ty, Ty)>,
353    unknowns: HashSet<String>,
354    found: HashMap<String, Ty>,
355}
356
357impl Tcx {
358    fn push<S: Into<String>>(&mut self, name: S, ty: Ty) {
359        let name = name.into();
360        match self.bindings.get_mut(&name) {
361            None => {
362                self.bindings.insert(name, vec![ty]);
363            }
364            Some(tys) => tys.push(ty),
365        }
366    }
367
368    fn pop(&mut self, name: &str) {
369        match self.bindings.get_mut(name) {
370            None => (),
371            Some(tys) => {
372                tys.pop();
373            }
374        }
375    }
376
377    fn peek(&self, name: &str) -> Option<Ty> {
378        match self.bindings.get(name) {
379            None => None,
380            Some(tys) => tys.last().copied(),
381        }
382    }
383}
384
385/// The initial typing context for cats. A iterator of architecture
386/// specific sets can be provided to this function. This will usually
387/// include the set of fences in the architecture.
388pub fn initial_tcx<I>(sets: I) -> Tcx
389where
390    I: Iterator<Item = String>,
391{
392    let mut bindings = HashMap::new();
393    let mut functions = HashMap::new();
394
395    // Architecture specific sets
396    for set in sets {
397        bindings.insert(set, vec![Ty::Set]);
398    }
399
400    functions.insert("domain".to_string(), (Ty::Rel, Ty::Set));
401    functions.insert("range".to_string(), (Ty::Rel, Ty::Set));
402    functions.insert("fencerel".to_string(), (Ty::Set, Ty::Rel));
403    functions.insert("ctrlcfence".to_string(), (Ty::Set, Ty::Rel));
404
405    Tcx { bindings, functions, unknowns: HashSet::new(), found: HashMap::new() }
406}
407
408/// For badly-typed cats.
409#[derive(Debug)]
410pub struct TyError {
411    message: String,
412}
413
414impl fmt::Display for TyError {
415    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
416        write!(f, "{}", self.message)
417    }
418}
419
420impl error::Error for TyError {
421    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
422        None
423    }
424}
425
426fn ty_error<S: Into<String>, A>(message: S) -> Result<A, TyError> {
427    Err(TyError { message: message.into() })
428}
429
430/// Returns the type of a cat expression.
431pub fn ty_of(exp: &Exp<Ty>) -> Ty {
432    use Exp::*;
433    match exp {
434        Empty(ty) => *ty,
435        Id(_, ty) => *ty,
436        Let(_, _, _, ty) => *ty,
437        TryWith(_, _, ty) => *ty,
438        Union(_, _, ty) => *ty,
439        Inter(_, _, ty) => *ty,
440        Diff(_, _, ty) => *ty,
441        Seq(_, _) => Ty::Rel,
442        Cartesian(_, _) => Ty::Rel,
443        Compl(_, ty) => *ty,
444        Identity(_) => Ty::Rel,
445        IdentityUnion(_) => Ty::Rel,
446        Inverse(_) => Ty::Rel,
447        App(_, _, ty) => *ty,
448    }
449}
450
451fn check_exp(tcx: &mut Tcx, exp: &Exp<()>, ty: Ty) -> Result<Exp<Ty>, TyError> {
452    use Exp::*;
453    match exp {
454        Empty(()) => Ok(Empty(ty)),
455
456        Id(id, ()) if tcx.unknowns.contains(id) => {
457            match tcx.found.insert(id.clone(), ty) {
458                Some(prev_ty) if ty != prev_ty => return ty_error(format!("Inconsistent type for {}", id)),
459                _ => (),
460            }
461            Ok(Id(id.clone(), ty))
462        }
463
464        _ => {
465            let exp = infer_exp(tcx, exp)?;
466            if ty == ty_of(&exp) {
467                Ok(exp)
468            } else {
469                ty_error("Types do not match")
470            }
471        }
472    }
473}
474
475macro_rules! unary_infer {
476    ($tcx: ident, $ctor: path, $x: ident) => {{
477        let x = infer_exp($tcx, $x)?;
478        let ty = ty_of(&x);
479        Ok($ctor(Box::new(x), ty))
480    }};
481}
482
483macro_rules! binary_infer {
484    ($tcx: ident, $ctor: path, $x: ident, $y: ident) => {{
485        match infer_exp($tcx, $x) {
486            Ok(x) => {
487                let y = check_exp($tcx, $y, ty_of(&x))?;
488                if ty_of(&x) == ty_of(&y) {
489                    let ty = ty_of(&x);
490                    Ok($ctor(Box::new(x), Box::new(y), ty))
491                } else {
492                    ty_error("Types do not match")
493                }
494            }
495            Err(_) => {
496                let y = infer_exp($tcx, $y)?;
497                let x = check_exp($tcx, $x, ty_of(&y))?;
498                if ty_of(&x) == ty_of(&y) {
499                    let ty = ty_of(&x);
500                    Ok($ctor(Box::new(x), Box::new(y), ty))
501                } else {
502                    ty_error("Types do not match")
503                }
504            }
505        }
506    }};
507}
508
509fn infer_exp(tcx: &mut Tcx, exp: &Exp<()>) -> Result<Exp<Ty>, TyError> {
510    use Exp::*;
511    match exp {
512        Empty(()) => ty_error("Cannot infer the type of an empty relation or set"),
513
514        Id(id, ()) => match tcx.peek(id) {
515            Some(ty) => Ok(Id(id.clone(), ty)),
516            None => ty_error(format!("Identifier {} not defined", id)),
517        },
518
519        Let(v, x, y, ()) => {
520            let x = infer_exp(tcx, x)?;
521            tcx.push(v, ty_of(&x));
522            let y = infer_exp(tcx, y)?;
523            tcx.pop(v);
524            let ty = ty_of(&y);
525            Ok(Let(v.clone(), Box::new(x), Box::new(y), ty))
526        }
527
528        TryWith(x, y, ()) => match infer_exp(tcx, x) {
529            Ok(x) => {
530                let y = check_exp(tcx, y, ty_of(&x))?;
531                if ty_of(&x) == ty_of(&y) {
532                    let ty = ty_of(&x);
533                    Ok(TryWith(Box::new(x), Box::new(y), ty))
534                } else {
535                    ty_error("Types do not mach in try with")
536                }
537            }
538            Err(_) => {
539                let y = infer_exp(tcx, y)?;
540                match check_exp(tcx, x, ty_of(&y)) {
541                    Ok(x) => {
542                        if ty_of(&x) == ty_of(&y) {
543                            let ty = ty_of(&x);
544                            Ok(TryWith(Box::new(x), Box::new(y), ty))
545                        } else {
546                            ty_error("Types do not match in try with")
547                        }
548                    }
549                    Err(_) => Ok(y),
550                }
551            }
552        },
553
554        Union(x, y, ()) => binary_infer!(tcx, Union, x, y),
555        Inter(x, y, ()) => binary_infer!(tcx, Inter, x, y),
556        Diff(x, y, ()) => binary_infer!(tcx, Diff, x, y),
557
558        Seq(x, y) => {
559            let x = check_exp(tcx, x, Ty::Rel)?;
560            let y = check_exp(tcx, y, Ty::Rel)?;
561            Ok(Seq(Box::new(x), Box::new(y)))
562        }
563
564        Cartesian(x, y) => {
565            let x = check_exp(tcx, x, Ty::Set)?;
566            let y = check_exp(tcx, y, Ty::Set)?;
567            Ok(Cartesian(Box::new(x), Box::new(y)))
568        }
569
570        Compl(x, ()) => unary_infer!(tcx, Compl, x),
571
572        Identity(x) => {
573            let x = check_exp(tcx, x, Ty::Set)?;
574            Ok(Identity(Box::new(x)))
575        }
576
577        IdentityUnion(x) => {
578            let x = check_exp(tcx, x, Ty::Rel)?;
579            Ok(IdentityUnion(Box::new(x)))
580        }
581
582        Inverse(x) => {
583            let x = check_exp(tcx, x, Ty::Rel)?;
584            Ok(Inverse(Box::new(x)))
585        }
586
587        App(f, x, ()) => {
588            let (from_ty, to_ty) = match tcx.functions.get(f) {
589                Some(f_ty) => *f_ty,
590                None => return ty_error(format!("Function {} not defined", f)),
591            };
592            let x = check_exp(tcx, x, from_ty)?;
593            Ok(App(f.clone(), Box::new(x), to_ty))
594        }
595    }
596}
597
598fn infer_def(tcx: &mut Tcx, def: Def<()>) -> Result<Def<Ty>, TyError> {
599    use Def::*;
600    Ok(match def {
601        Set(name) => {
602            tcx.push(name.clone(), Ty::Set);
603            Set(name)
604        }
605        
606        Relation(name) => {
607            tcx.push(name.clone(), Ty::Rel);
608            Relation(name)
609        }
610        
611        Let(mut bindings) => {
612            let bindings: Vec<(String, Exp<Ty>)> = bindings
613                .drain(..)
614                .map(|(name, exp)| match infer_exp(tcx, &exp) {
615                    Ok(exp) => Ok((name, exp)),
616                    Err(e) => Err(e),
617                })
618                .collect::<Result<_, _>>()?;
619
620            tcx.unknowns.clear();
621            tcx.found.clear();
622
623            for (name, exp) in &bindings {
624                tcx.push(name, ty_of(exp));
625            }
626
627            Let(bindings)
628        }
629
630        Fn(name, mut params, body) => {
631            for (param, _) in &params {
632                tcx.unknowns.insert(param.clone());
633            }
634
635            let body = infer_exp(tcx, &body)?;
636
637            let params: Vec<(String, Ty)> = params
638                .drain(..)
639                .map(|(param, _)| {
640                    if let Some(ty) = tcx.found.get(&param) {
641                        Ok((param, *ty))
642                    } else {
643                        ty_error(format!("Could not infer type of function parameter {}", param))
644                    }
645                })
646                .collect::<Result<_, _>>()?;
647
648            tcx.unknowns.clear();
649            tcx.found.clear();
650
651            Fn(name, params, body)
652        }
653
654        TClosure(id, exp) => {
655            let def = TClosure(id.clone(), check_exp(tcx, &exp, Ty::Rel)?);
656            tcx.push(id, Ty::Rel);
657            def
658        }
659
660        RTClosure(id, exp) => {
661            let def = RTClosure(id.clone(), check_exp(tcx, &exp, Ty::Rel)?);
662            tcx.push(id, Ty::Rel);
663            def
664        }
665
666        Flag(check, exp, id) => Flag(check, infer_exp(tcx, &exp)?, id),
667
668        Check(check, exp, opt_id) => Check(check, infer_exp(tcx, &exp)?, opt_id),
669
670        Show(ids) => Show(ids),
671
672        ShowAs(exp, id) => ShowAs(check_exp(tcx, &exp, Ty::Rel)?, id),
673
674        Unshow(ids) => Unshow(ids),
675    })
676}
677
678/// Infer all the types within a cat.
679pub fn infer_cat(tcx: &mut Tcx, mut cat: Cat<()>) -> Result<Cat<Ty>, TyError> {
680    Ok(Cat { tag: cat.tag, defs: cat.defs.drain(..).map(|def| infer_def(tcx, def)).collect::<Result<_, _>>()? })
681}
682
683#[cfg(test)]
684mod tests {
685    use super::*;
686
687    #[test]
688    fn test_local_shadowing() {
689        use Exp::*;
690        let x = "x".to_string();
691        let mut exp = Let(
692            x.clone(),
693            Box::new(Id(x.clone(), ())),
694            Box::new(Let(x.clone(), Box::new(Id(x.clone(), ())), Box::new(Id(x.clone(), ())), ())),
695            (),
696        );
697        exp.unshadow(&mut Shadows::new(), &mut HashMap::new());
698        assert_eq!(
699            "Let(\"x/l0\", Id(\"x\", ()), Let(\"x/l1\", Id(\"x/l0\", ()), Id(\"x/l1\", ()), ()), ())",
700            &format!("{:?}", exp)
701        )
702    }
703}