├── .gitignore ├── NorvigUnification.png ├── src ├── lists.pl ├── lib.rs ├── example.pl ├── prolog.pest ├── term.rs ├── main.rs ├── database.rs ├── unify.rs ├── parser.rs └── prover.rs ├── Cargo.toml ├── README.md ├── .github └── workflows │ └── rust.yml ├── zebra.pl └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /NorvigUnification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aarroyoc/esgueva/master/NorvigUnification.png -------------------------------------------------------------------------------- /src/lists.pl: -------------------------------------------------------------------------------- 1 | member(X, [X|Xs]). 2 | member(X, [Y|Xs]) :- member(X, Xs). 3 | 4 | list([a,b,c]). 5 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod database; 2 | pub mod unify; 3 | pub mod prover; 4 | pub mod term; 5 | pub mod parser; 6 | -------------------------------------------------------------------------------- /src/example.pl: -------------------------------------------------------------------------------- 1 | likes(kim, robin). 2 | likes(sandy, lee). 3 | likes(sandy, kim). 4 | likes(robin, cats). 5 | likes(sandy, X) :- likes(X, cats). 6 | likes(kim, X) :- likes(X, lee), likes(X, kim). 7 | likes(X, X). 8 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "esgueva" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies.uuid] 9 | version = "1.2.2" 10 | features = ["v1", "std", "rng"] 11 | 12 | [dependencies.nom] 13 | version = "7" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Esgueva 2 | 3 | Toy Prolog system made for fun and give myself a better understanding of how a Prolog system works. Do not use it. For any serious development, consider [Scryer Prolog](https://github.com/mthom/scryer-prolog/) or [Trealla Prolog](https://github.com/infradig/trealla) 4 | 5 | ## Example 6 | 7 | ``` 8 | cargo build --release 9 | cargo run -- zebra.pl 10 | 11 | ?- zebra(H, W, Z). 12 | ``` 13 | 14 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-22.04 16 | 17 | steps: 18 | - uses: actions/checkout@v3 19 | - name: Build 20 | run: cargo build --verbose 21 | - name: Run tests 22 | run: cargo test --verbose 23 | -------------------------------------------------------------------------------- /src/prolog.pest: -------------------------------------------------------------------------------- 1 | number = @{ 2 | ASCII_DIGIT+ 3 | } 4 | 5 | variable = @{ "_" | (ASCII_ALPHA_UPPER ~ ASCII_ALPHANUMERIC*)} 6 | 7 | atom = @{ ASCII_ALPHA_LOWER ~ (ASCII_ALPHANUMERIC | "_")* } 8 | 9 | term = { 10 | structure | atom | number | variable 11 | } 12 | 13 | structure = { 14 | ( atom ~ "(" ~ term ~ ("," ~ term)* ~ ")" ) 15 | } 16 | 17 | rule = { 18 | term ~ "." 19 | } 20 | 21 | prolog = { SOI ~ (rule)+ ~ EOI} 22 | 23 | WHITESPACE = _{ " " | "\t" | "\n" } 24 | COMMENT = _{ ("%" ~ (!"\n" ~ ANY)*) | ("/*" ~ (!"*/" ~ ANY)* ~ "*/") } -------------------------------------------------------------------------------- /src/term.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | #[derive(Debug, Clone)] 4 | pub enum Term { 5 | Atom(String), 6 | Var(String), 7 | Str(String, Vec), 8 | } 9 | 10 | impl PartialEq for Term { 11 | fn eq(&self, other: &Self) -> bool { 12 | match (self, other) { 13 | (Term::Atom(x), Term::Atom(y)) => x == y, 14 | (Term::Var(x), Term::Var(y)) => x == y, 15 | (Term::Str(f_x, args_x), Term::Str(f_y, args_y)) => f_x == f_y && args_x == args_y, 16 | _ => false 17 | } 18 | } 19 | } 20 | 21 | impl fmt::Display for Term { 22 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 23 | match self { 24 | Term::Atom(x) => write!(f, "{}", x), 25 | Term::Var(x) => write!(f, "{}", x), 26 | Term::Str(functor, args) => write!(f, "{}({})", functor, args.iter().map(|t| format!("{}", t)).collect::>().join(",")) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /zebra.pl: -------------------------------------------------------------------------------- 1 | member(X, [X|Xs]). 2 | member(X, [Y|Xs]) :- member(X, Xs). 3 | 4 | nextto(X, Y, List) :- iright(X, Y, List). 5 | nextto(X, Y, List) :- iright(Y, X, List). 6 | 7 | iright(Left, Right, [Left|[Right|Xs]]). 8 | iright(Left, Right, [X|Xs]) :- iright(Left, Right, Xs). 9 | 10 | eq(X, X). 11 | 12 | 13 | zebra(H, W, Z) :- eq(H, [house(norwegian, X1, X2, X3, X4), X5, house(X6, X7, X8, milk, X9), X10, X11]), member(house(englishman, A1, A2, A3, red), H), member(house(spaniard, dog, B1, B2, B3), H), member(house(C1, C2, C3, coffee, green), H), member(house(ukrainian, D1, D2, tea, D3), H), iright(house(E1, E2, E3, E4, ivory), house(F1, F2, F3, F4, green), H), member(house(G1, snails, winston, G2, G3), H), member(house(H1, H2, kools, H3, yellow), H), nextto(house(I1, I2, chesterfield, I3, I4), house(J1, fox, J2, J3, J4), H), nextto(house(K1, K2, kools, K3, K4), house(L1, horse, L2, L3, L4), H), member(house(M1, M2, luckystrike, orangejuice, M3), H), member(house(japanese, N1, parliaments, N2, N3), H), nextto(house(norwegian, O1, O2, O3, O4), house(P1, P2, P3, P4, blue), H), member(house(W, R1, R2, water, R3), H), member(house(Z, zebra, S1, S2, S3), H). 14 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs; 3 | use std::io; 4 | use std::io::Write; 5 | use std::collections::HashMap; 6 | 7 | use esgueva::database::Database; 8 | use esgueva::parser; 9 | use esgueva::prover; 10 | use esgueva::term::Term; 11 | 12 | fn main() { 13 | println!("Esgueva Prolog 0.1.0 - Adrián Arroyo Calle 2022"); 14 | let args: Vec = env::args().collect(); 15 | 16 | match args.len() { 17 | 2 => { 18 | if args[1] == "-h" { 19 | print_help(); 20 | } else { 21 | repl(file_to_database(&args[1])) 22 | } 23 | } 24 | 1 => repl(Database::new()), 25 | _ => print_help() 26 | } 27 | 28 | } 29 | 30 | fn print_help() { 31 | println!("Usage: esgueva [PROLOG FILE]\tStart Esgueva top-level optionally loading a file"); 32 | println!(" esgueva -h\t\tShow help"); 33 | } 34 | 35 | fn file_to_database(file: &str) -> Database { 36 | let mut db = Database::new(); 37 | let contents = fs::read_to_string(file).expect("File must exist"); 38 | 39 | if let Ok((_, clauses)) = parser::file(&contents) { 40 | for clause in clauses { 41 | db.add_clause(clause); 42 | } 43 | } else { 44 | eprintln!("Error loading file: {}", file); 45 | } 46 | 47 | db 48 | } 49 | 50 | fn repl(database: Database) { 51 | loop { 52 | print!("?- "); 53 | io::stdout().flush().unwrap(); 54 | let mut input = String::new(); 55 | io::stdin().read_line(&mut input).unwrap(); 56 | if let Ok((_, mut goals)) = parser::clause_body(&input) { 57 | let vars_in_goals = prover::find_variables_in_goals(&goals); 58 | goals.push(Term::Atom("__backtracking?".into())); 59 | prover::prove_all(goals.into(), Some(HashMap::new()), &database, &vars_in_goals); 60 | println!("false."); 61 | } else { 62 | eprintln!("Can't parse query!"); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/database.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use crate::term::Term; 4 | 5 | #[derive(PartialEq, Debug)] 6 | pub struct Clause { 7 | pub head: Term, 8 | pub body: Vec, 9 | } 10 | 11 | #[derive(PartialEq, Eq, Hash, Clone)] 12 | pub struct Predicate { 13 | pub name: String, 14 | arity: usize, 15 | } 16 | impl Predicate { 17 | pub fn from_clause(clause: &Clause) -> Option { 18 | Self::from_term(&clause.head) 19 | } 20 | 21 | pub fn from_term(term: &Term) -> Option { 22 | match term.clone() { 23 | Term::Str(f, args) => Some(Predicate { 24 | name: f.clone(), 25 | arity: args.len(), 26 | }), 27 | Term::Atom(f) => Some(Predicate { 28 | name: f.clone(), 29 | arity: 0, 30 | }), 31 | _ => None 32 | } 33 | } 34 | } 35 | 36 | pub struct Database { 37 | data: HashMap> 38 | } 39 | 40 | impl Database { 41 | pub fn new() -> Self { 42 | Database { 43 | data: HashMap::new() 44 | } 45 | } 46 | 47 | pub fn add_clause(&mut self, clause: Clause) { 48 | if let Some(predicate_key) = Predicate::from_clause(&clause) { 49 | { 50 | 51 | self.data.entry(predicate_key.clone()).or_insert(Vec::new()); 52 | } 53 | { 54 | let predicate = self.data.get_mut(&predicate_key).unwrap(); 55 | predicate.push(clause); 56 | } 57 | } 58 | } 59 | 60 | pub fn get_clauses(&self, predicate: &Predicate) -> Option<&Vec> { 61 | self.data.get(predicate) 62 | } 63 | 64 | pub fn clear_all(&mut self) { 65 | self.data = HashMap::new(); 66 | } 67 | 68 | pub fn clear_predicate(&mut self, predicate: &Predicate) { 69 | self.data.remove(predicate); 70 | } 71 | } 72 | 73 | #[test] 74 | fn add_rule() { 75 | let clause = Clause { 76 | head: Term::Str("mortal".into(), vec![Term::Var("X".into())]), 77 | body: vec![Term::Str("human".into(), vec![Term::Var("X".into())])], 78 | }; 79 | 80 | let mut db = Database::new(); 81 | db.add_clause(clause); 82 | let clauses = db.get_clauses(&Predicate { name: "mortal".into(), arity: 1}).unwrap(); 83 | assert_eq!(clauses.len(), 1); 84 | } 85 | 86 | #[test] 87 | fn add_fact() { 88 | let clause = Clause { 89 | head: Term::Str("human".into(), vec![Term::Atom("socrates".into())]), 90 | body: vec![Term::Atom("true".into())], 91 | }; 92 | 93 | let mut db = Database::new(); 94 | db.add_clause(clause); 95 | let clauses = db.get_clauses(&Predicate { name: "human".into(), arity: 1}).unwrap(); 96 | assert_eq!(clauses.len(), 1); 97 | assert_eq!(clauses[0].body, vec![Term::Atom("true".into())]); 98 | let clauses = db.get_clauses(&Predicate { name: "human".into(), arity: 0}); 99 | assert!(clauses.is_none()); 100 | } 101 | -------------------------------------------------------------------------------- /src/unify.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::iter::zip; 3 | 4 | use crate::term::Term; 5 | 6 | pub type Bindings = Option>; 7 | 8 | pub fn unify(x: Term, y: Term, bindings: Bindings, occurs_check: bool) -> Bindings { 9 | if x == y { 10 | bindings 11 | } else if let Term::Var(var) = x { 12 | unify_variable(var, y, bindings, occurs_check) 13 | } else if let Term::Var(var) = y { 14 | unify_variable(var, x, bindings, occurs_check) 15 | } else if let (Term::Str(f_x, args_x), Term::Str(f_y, args_y)) = (x, y) { 16 | if f_x == f_y && args_x.len() == args_y.len() { 17 | zip(args_x, args_y).fold(bindings, |acc_bindings, (t_x, t_y)| { 18 | unify(t_x, t_y, acc_bindings, occurs_check) 19 | }) 20 | } else { 21 | None 22 | } 23 | } else { 24 | None 25 | } 26 | } 27 | 28 | #[inline] 29 | fn unify_variable(var: String, y: Term, bindings: Bindings, occurs_check_flag: bool) -> Bindings { 30 | let bindings = bindings?; 31 | match bindings.get(&var) { 32 | Some(value) => unify(value.clone(), y, Some(bindings), occurs_check_flag), 33 | None => { 34 | if let Term::Var(ref y_var) = y { 35 | if let Some(y_val) = bindings.get(y_var) { 36 | return unify(Term::Var(var), y_val.clone(), Some(bindings), occurs_check_flag) 37 | } 38 | } 39 | 40 | occurs_check(var.clone(), y.clone(), Some(bindings), occurs_check_flag).and_then(|mut bindings| { 41 | bindings.insert(var, y.clone()); 42 | Some(bindings) 43 | }) 44 | 45 | } 46 | } 47 | } 48 | 49 | #[inline] 50 | fn occurs_check(var: String, y: Term, bindings: Bindings, occurs_check_flag: bool) -> Bindings { 51 | if !occurs_check_flag { 52 | bindings 53 | } else if Term::Var(var.clone()) == y { 54 | None 55 | } else if let Term::Var(y_var) = y { 56 | match bindings.clone()?.get(&y_var) { 57 | Some(y_val) => { 58 | return occurs_check(var, y_val.clone(), bindings, occurs_check_flag); 59 | } 60 | None => bindings 61 | } 62 | } else if let Term::Str(_, args) = y { 63 | args.iter().fold(bindings, |acc_bindings, t_y| { 64 | occurs_check(var.clone(), t_y.clone(), acc_bindings, occurs_check_flag) 65 | }) 66 | } else { 67 | bindings 68 | } 69 | } 70 | 71 | #[test] 72 | fn unify_atoms() { 73 | let x = Term::Atom("duero".into()); 74 | let y = Term::Atom("duero".into()); 75 | let bindings = unify(x, y, Some(HashMap::new()), false); 76 | let expected = Some(HashMap::new()); 77 | assert_eq!(expected, bindings); 78 | } 79 | 80 | #[test] 81 | fn unify_atoms_fail() { 82 | let x = Term::Atom("duero".into()); 83 | let y = Term::Atom("pisuerga".into()); 84 | let bindings = unify(x, y, Some(HashMap::new()), false); 85 | let expected = None; 86 | assert_eq!(expected, bindings); 87 | } 88 | 89 | #[test] 90 | fn unify_atom_var() { 91 | let x = Term::Var("River".into()); 92 | let y = Term::Atom("duero".into()); 93 | let bindings = unify(x, y, Some(HashMap::new()), false); 94 | let mut expected = HashMap::new(); 95 | expected.insert("River".into(), Term::Atom("duero".into())); 96 | assert_eq!(Some(expected), bindings); 97 | } 98 | 99 | #[test] 100 | fn unify_atom_var_2() { 101 | let y = Term::Var("River".into()); 102 | let x = Term::Atom("duero".into()); 103 | let bindings = unify(x, y, Some(HashMap::new()), false); 104 | let mut expected = HashMap::new(); 105 | expected.insert("River".into(), Term::Atom("duero".into())); 106 | assert_eq!(Some(expected), bindings); 107 | } 108 | 109 | #[test] 110 | fn unify_var() { 111 | let x = Term::Var("X".into()); 112 | let y = Term::Var("Y".into()); 113 | let bindings = unify(x, y, Some(HashMap::new()), false); 114 | let mut expected = HashMap::new(); 115 | expected.insert("X".into(), Term::Var("Y".into())); 116 | assert_eq!(Some(expected), bindings); 117 | } 118 | 119 | #[test] 120 | fn unify_str() { 121 | let x = Term::Str("f".into(), vec![Term::Var("X".into()), Term::Atom("b".into())]); 122 | let y = Term::Str("f".into(), vec![Term::Atom("a".into()), Term::Var("Y".into())]); 123 | let bindings = unify(x, y, Some(HashMap::new()), false); 124 | let mut expected = HashMap::new(); 125 | expected.insert("X".into(), Term::Atom("a".into())); 126 | expected.insert("Y".into(), Term::Atom("b".into())); 127 | assert_eq!(Some(expected), bindings); 128 | } 129 | 130 | #[test] 131 | fn unify_str_fail() { 132 | let x = Term::Str("f".into(), vec![Term::Var("X".into()), Term::Atom("b".into())]); 133 | let y = Term::Str("g".into(), vec![Term::Atom("a".into()), Term::Var("Y".into())]); 134 | let bindings = unify(x, y, Some(HashMap::new()), false); 135 | assert_eq!(None, bindings); 136 | } 137 | 138 | #[test] 139 | fn unify_str_fail_2() { 140 | let x = Term::Str("f".into(), vec![Term::Var("X".into()), Term::Atom("b".into())]); 141 | let y = Term::Str("f".into(), vec![Term::Atom("a".into())]); 142 | let bindings = unify(x, y, Some(HashMap::new()), false); 143 | assert_eq!(None, bindings); 144 | } 145 | 146 | #[test] 147 | fn unify_fxy_norvig_bug() { 148 | let x = Term::Str("f".into(), vec![Term::Var("X".into()), Term::Var("Y".into())]); 149 | let y = Term::Str("f".into(), vec![Term::Var("Y".into()), Term::Var("X".into())]); 150 | let bindings = unify(x, y, Some(HashMap::new()), false); 151 | let mut expected = HashMap::new(); 152 | expected.insert("X".into(), Term::Var("Y".into())); 153 | assert_eq!(Some(expected), bindings); 154 | } 155 | 156 | #[test] 157 | fn unify_cyclic() { 158 | let x = Term::Var("X".into()); 159 | let y = Term::Str("f".into(), vec![Term::Var("X".into())]); 160 | let bindings = unify(x, y, Some(HashMap::new()), true); 161 | assert_eq!(None, bindings); 162 | } 163 | -------------------------------------------------------------------------------- /src/parser.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use nom::{ 3 | IResult, 4 | Err, 5 | error::Error, 6 | error::ErrorKind, 7 | branch::alt, 8 | bytes::complete::tag, 9 | bytes::complete::is_not, 10 | character::complete::anychar, 11 | character::complete::char, 12 | character::complete::alphanumeric0, 13 | character::complete::multispace1, 14 | multi::many0, 15 | multi::many1, 16 | multi::separated_list0, 17 | multi::separated_list1, 18 | sequence::delimited, 19 | }; 20 | 21 | use crate::term::Term; 22 | use crate::database::Clause; 23 | 24 | pub fn file(input: &str) -> IResult<&str, Vec> { 25 | let (input, clauses) = separated_list0(multispace1, clause)(input)?; 26 | 27 | Ok((input, clauses)) 28 | } 29 | 30 | fn clause(input: &str) -> IResult<&str, Clause> { 31 | let (input, clause) = alt((clause_fact, clause_rule))(input)?; 32 | 33 | Ok((input, clause)) 34 | } 35 | 36 | fn clause_fact(input: &str) -> IResult<&str, Clause> { 37 | let (input, head) = alt((term_str, term_atom))(input)?; 38 | let (input, _) = char('.')(input)?; 39 | 40 | Ok((input, Clause { head, body: vec![] })) 41 | } 42 | 43 | fn clause_rule(input: &str) -> IResult<&str, Clause> { 44 | let (input, head) = alt((term_str, term_atom))(input)?; 45 | let (input, _) = many1(char(' '))(input)?; 46 | let (input, _) = tag(":-")(input)?; 47 | let (input, _) = many1(char(' '))(input)?; 48 | let (input, body) = clause_body(input)?; 49 | 50 | Ok((input, Clause { head, body })) 51 | } 52 | 53 | pub fn clause_body(input: &str) -> IResult<&str, Vec> { 54 | let (input, goals) = separated_list1(spaced_comma, alt((term_str, term_atom)))(input)?; 55 | let (input, _) = char('.')(input)?; 56 | 57 | Ok((input, goals)) 58 | } 59 | 60 | fn spaced_comma(input: &str) -> IResult<&str, ()> { 61 | let (input, _) = many0(char(' '))(input)?; 62 | let (input, _) = char(',')(input)?; 63 | let (input, _) = many0(char(' '))(input)?; 64 | 65 | Ok((input, ())) 66 | } 67 | 68 | fn term_str(input: &str) -> IResult<&str, Term> { 69 | alt((term_str_default, term_str_quoted, term_str_list, term_str_head_tail))(input) 70 | } 71 | 72 | fn term_str_default(input: &str) -> IResult<&str, Term> { 73 | let (input, first) = anychar(input)?; 74 | if !first.is_ascii_lowercase() { 75 | return Err(Err::Error(Error::new(input, ErrorKind::Char))); 76 | } 77 | let (input, atom) = alphanumeric0(input)?; 78 | 79 | let (input, _) = char('(')(input)?; 80 | 81 | let (input, args) = separated_list1(spaced_comma, alt((term_str, term_var, term_atom)))(input)?; 82 | 83 | let (input, _) = char(')')(input)?; 84 | 85 | Ok((input, Term::Str(format!("{}{}", first, atom), args))) 86 | } 87 | 88 | fn term_str_quoted(input: &str) -> IResult<&str, Term> { 89 | let (input, atom) = delimited(char('\''), is_not("'"), char('\''))(input)?; 90 | let (input, _) = char('(')(input)?; 91 | 92 | let (input, args) = separated_list1(spaced_comma, alt((term_str, term_var, term_atom)))(input)?; 93 | 94 | let (input, _) = char(')')(input)?; 95 | 96 | Ok((input, Term::Str(atom.to_string(), args))) 97 | } 98 | 99 | fn term_str_list(input: &str) -> IResult<&str, Term> { 100 | let (input, _) = char('[')(input)?; 101 | let (input, elements) = separated_list1(spaced_comma, alt((term_str, term_var, term_atom)))(input)?; 102 | let (input, _) = char(']')(input)?; 103 | 104 | let list = build_list(elements.into()); 105 | 106 | Ok((input, list)) 107 | } 108 | 109 | fn build_list(mut elements: VecDeque) -> Term { 110 | if let Some(element) = elements.pop_front() { 111 | Term::Str(".".into(), vec![element, build_list(elements)]) 112 | } else { 113 | Term::Atom("[]".into()) 114 | } 115 | } 116 | 117 | fn term_str_head_tail(input: &str) -> IResult<&str, Term> { 118 | let (input, _) = char('[')(input)?; 119 | let (input, head) = alt((term_str, term_var, term_atom))(input)?; 120 | let (input, _) = char('|')(input)?; 121 | let (input, tail) = alt((term_str, term_var, term_atom))(input)?; 122 | let (input, _) = char(']')(input)?; 123 | 124 | Ok((input, Term::Str(".".into(), vec![head, tail]))) 125 | } 126 | 127 | fn term_var(input: &str) -> IResult<&str, Term> { 128 | let (input, first) = anychar(input)?; 129 | if !first.is_ascii_uppercase() { 130 | return Err(Err::Error(Error::new(input, ErrorKind::Char))); 131 | } 132 | let (input, var) = alphanumeric0(input)?; 133 | 134 | Ok((input, Term::Var(format!("{}{}", first, var)))) 135 | } 136 | 137 | fn term_atom(input: &str) -> IResult<&str, Term> { 138 | alt((term_atom_default, term_atom_quoted, term_atom_nil))(input) 139 | } 140 | 141 | fn term_atom_default(input: &str) -> IResult<&str, Term> { 142 | let (input, first) = anychar(input)?; 143 | if !first.is_ascii_lowercase() { 144 | return Err(Err::Error(Error::new(input, ErrorKind::Char))); 145 | } 146 | let (input, atom) = alphanumeric0(input)?; 147 | 148 | Ok((input, Term::Atom(format!("{}{}", first, atom)))) 149 | } 150 | 151 | fn term_atom_quoted(input: &str) -> IResult<&str, Term> { 152 | let (input, atom) = delimited(char('\''), is_not("'"), char('\''))(input)?; 153 | 154 | Ok((input, Term::Atom(atom.to_string()))) 155 | } 156 | 157 | fn term_atom_nil(input: &str) -> IResult<&str, Term> { 158 | let (input, _) = tag("[]")(input)?; 159 | 160 | Ok((input, Term::Atom("[]".into()))) 161 | } 162 | 163 | #[test] 164 | fn parse1() { 165 | let input = "f(X,b,g(T)), g(X, a, Z)."; 166 | let result = clause_body(input); 167 | let expected = vec![ 168 | Term::Str("f".into(), vec![Term::Var("X".into()), Term::Atom("b".into()), Term::Str("g".into(), vec![Term::Var("T".into())])]), 169 | Term::Str("g".into(), vec![Term::Var("X".into()), Term::Atom("a".into()), Term::Var("Z".into())]), 170 | ]; 171 | assert_eq!(result, Ok(("", expected))); 172 | } 173 | 174 | #[test] 175 | fn parse_fact() { 176 | let input = "f(adrian, valladolid)."; 177 | let result = clause(input); 178 | let expected = Clause { 179 | head: Term::Str("f".into(), vec![Term::Atom("adrian".into()), Term::Atom("valladolid".into())]), 180 | body: vec![], 181 | }; 182 | assert_eq!(result, Ok(("", expected))); 183 | } 184 | 185 | #[test] 186 | fn parse_fact_2() { 187 | let input = "list('.'(a,nil))."; 188 | let result = clause(input); 189 | let expected = Clause { 190 | head: Term::Str("list".into(), vec![Term::Str(".".into(), vec![Term::Atom("a".into()), Term::Atom("nil".into())])]), 191 | body: vec![], 192 | }; 193 | assert_eq!(result, Ok(("", expected))); 194 | } 195 | 196 | #[test] 197 | fn parse_fact_3() { 198 | let input = "list([a,[b,c]])."; 199 | let result = clause(input); 200 | let expected = Clause { 201 | head: Term::Str("list".into(), vec![ 202 | Term::Str(".".into(), vec![ 203 | Term::Atom("a".into()), Term::Str(".".into(), vec![ 204 | Term::Str(".".into(), vec![ 205 | Term::Atom("b".into()), 206 | Term::Str(".".into(), vec![ 207 | Term::Atom("c".into()), 208 | Term::Atom("[]".into()) 209 | ]) 210 | ]), 211 | Term::Atom("[]".into()) 212 | ])])]), 213 | body: vec![], 214 | }; 215 | assert_eq!(result, Ok(("", expected))); 216 | } 217 | 218 | #[test] 219 | fn parse_fact_4() { 220 | let input = "list([X|Xs])."; 221 | let result = clause(input); 222 | let expected = Clause { 223 | head: Term::Str("list".into(), vec![Term::Str(".".into(), vec![Term::Var("X".into()), Term::Var("Xs".into())])]), 224 | body: vec![], 225 | }; 226 | assert_eq!(result, Ok(("", expected))); 227 | } 228 | 229 | #[test] 230 | fn parse_rule() { 231 | let input = "likes(X, sandy) :- likes(X, cats), likes(X, kim)."; 232 | let result = clause(input); 233 | let expected = Clause { 234 | head: Term::Str("likes".into(), vec![Term::Var("X".into()), Term::Atom("sandy".into())]), 235 | body: vec![ 236 | Term::Str("likes".into(), vec![Term::Var("X".into()), Term::Atom("cats".into())]), 237 | Term::Str("likes".into(), vec![Term::Var("X".into()), Term::Atom("kim".into())]), 238 | ], 239 | }; 240 | assert_eq!(result, Ok(("", expected))); 241 | } 242 | 243 | #[test] 244 | fn parse_file() { 245 | let input = include_str!("example.pl"); 246 | let result = file(input); 247 | let expected = vec![ 248 | Clause { 249 | head: Term::Str("likes".into(), vec![Term::Atom("kim".into()), Term::Atom("robin".into())]), 250 | body: vec![], 251 | }, 252 | Clause { 253 | head: Term::Str("likes".into(), vec![Term::Atom("sandy".into()), Term::Atom("lee".into())]), 254 | body: vec![], 255 | }, 256 | Clause { 257 | head: Term::Str("likes".into(), vec![Term::Atom("sandy".into()), Term::Atom("kim".into())]), 258 | body: vec![], 259 | }, 260 | Clause { 261 | head: Term::Str("likes".into(), vec![Term::Atom("robin".into()), Term::Atom("cats".into())]), 262 | body: vec![], 263 | }, 264 | Clause { 265 | head: Term::Str("likes".into(), vec![Term::Atom("sandy".into()), Term::Var("X".into())]), 266 | body: vec![ 267 | Term::Str("likes".into(), vec![Term::Var("X".into()), Term::Atom("cats".into())]) 268 | ], 269 | }, 270 | Clause { 271 | head: Term::Str("likes".into(), vec![Term::Atom("kim".into()), Term::Var("X".into())]), 272 | body: vec![ 273 | Term::Str("likes".into(), vec![Term::Var("X".into()), Term::Atom("lee".into())]), 274 | Term::Str("likes".into(), vec![Term::Var("X".into()), Term::Atom("kim".into())]) 275 | ], 276 | }, 277 | Clause { 278 | head: Term::Str("likes".into(), vec![Term::Var("X".into()), Term::Var("X".into())]), 279 | body: vec![], 280 | } 281 | ]; 282 | assert_eq!(result, Ok(("\n", expected))); 283 | } 284 | -------------------------------------------------------------------------------- /src/prover.rs: -------------------------------------------------------------------------------- 1 | use uuid::Uuid; 2 | use std::collections::HashMap; 3 | use std::collections::HashSet; 4 | use std::collections::VecDeque; 5 | 6 | use crate::term::Term; 7 | use crate::unify::{Bindings, unify}; 8 | use crate::database::{Database, Predicate, Clause}; 9 | 10 | fn prove(goal: Term, bindings: Bindings, database: &Database, other_goals: VecDeque, vars_in_goals: &HashSet) -> Bindings { 11 | if let Some(predicate) = Predicate::from_term(&goal) { 12 | if &predicate.name == "__backtracking?" { 13 | let mut line = Vec::new(); 14 | for var in vars_in_goals { 15 | line.push(format!("{} = {}", var, subst_bindings(bindings.clone(), Term::Var(var.clone())))); 16 | } 17 | if line.len() == 0 { 18 | println!("true"); 19 | } else { 20 | println!("{}", line.join(",")); 21 | } 22 | if ask_confirm() { 23 | None 24 | } else { 25 | bindings 26 | } 27 | } else { 28 | if let Some(clauses) = database.get_clauses(&predicate) { 29 | for clause in clauses { 30 | let renamed_clause = rename_variables(&clause); 31 | let bindings = unify(goal.clone(), renamed_clause.head, bindings.clone(), false); 32 | if bindings.is_none() { 33 | // do nothing 34 | } else { 35 | let mut goals = VecDeque::from(renamed_clause.body.clone()); 36 | goals.append(&mut other_goals.clone()); 37 | let new_bindings = prove_all(goals, bindings, database, vars_in_goals); 38 | if new_bindings.is_some() { 39 | return new_bindings; 40 | } 41 | } 42 | } 43 | None 44 | } else { 45 | None 46 | } 47 | } 48 | } else { 49 | None 50 | } 51 | } 52 | 53 | pub fn prove_all(mut goals: VecDeque, bindings: Bindings, database: &Database, vars_in_goals: &HashSet) -> Bindings { 54 | if let Some(goal) = goals.pop_front() { 55 | prove(goal, bindings, database, goals, vars_in_goals) 56 | } else { 57 | bindings 58 | } 59 | } 60 | 61 | fn batch_prove(goal: Term, bindings: Bindings, database: &Database) -> Option> { 62 | if let Some(predicate) = Predicate::from_term(&goal) { 63 | if let Some(clauses) = database.get_clauses(&predicate) { 64 | let mut solutions = Vec::new(); 65 | for clause in clauses { 66 | let renamed_clause = rename_variables(&clause); 67 | let bindings = unify(goal.clone(), renamed_clause.head, bindings.clone(), false); 68 | if bindings.is_none() { 69 | // do nothing 70 | } else if renamed_clause.body.len() == 0 { 71 | solutions.push(bindings); 72 | } else { 73 | if let Some(mut all_solutions) = batch_prove_all(renamed_clause.body, vec![bindings], database) { 74 | solutions.append(&mut all_solutions); 75 | } 76 | } 77 | } 78 | if solutions.len() == 0 { 79 | None 80 | } else { 81 | Some(solutions) 82 | } 83 | } else { 84 | None 85 | } 86 | } else { 87 | None 88 | } 89 | } 90 | 91 | fn batch_prove_all(mut goals: Vec, bindings: Vec, database: &Database) -> Option> { 92 | if let Some(goal) = goals.pop() { 93 | let mut solutions = Vec::new(); 94 | for binding in bindings { 95 | if let Some(mut goal_solutions) = batch_prove(goal.clone(), binding, database) { 96 | solutions.append(&mut goal_solutions); 97 | } 98 | } 99 | if solutions.len() == 0 { 100 | None 101 | } else { 102 | batch_prove_all(goals, solutions, database) 103 | } 104 | } else { 105 | Some(bindings) 106 | } 107 | } 108 | 109 | fn rename_variables(clause: &Clause) -> Clause { 110 | let mut bindings = HashMap::new(); 111 | Clause { 112 | head: rename_term(&clause.head, &mut bindings), 113 | body: clause.body.iter().map(|term| rename_term(term, &mut bindings)).collect(), 114 | } 115 | } 116 | 117 | fn rename_term(term: &Term, bindings: &mut HashMap) -> Term { 118 | match term { 119 | Term::Atom(f) => Term::Atom(f.clone()), 120 | Term::Var(var) => { 121 | if let Some(subst) = bindings.get(var) { 122 | Term::Var(subst.clone()) 123 | } else { 124 | let id = Uuid::now_v1(&[1, 2, 3, 4, 5, 6]).to_string(); 125 | bindings.insert(var.clone(), id.clone()); 126 | Term::Var(id) 127 | } 128 | }, 129 | Term::Str(f, args) => { 130 | Term::Str(f.clone(), args.iter().map(|arg| rename_term(arg, bindings)).collect()) 131 | } 132 | } 133 | } 134 | 135 | fn top_level_prove(goals: Vec, database: &Database) -> String { 136 | let vars_in_goals = find_variables_in_goals(&goals); 137 | let solutions = batch_prove_all(goals, vec![Some(HashMap::new())], database); 138 | 139 | if let Some(solutions) = solutions { 140 | let mut output = Vec::new(); 141 | for solution in solutions { 142 | let mut line = Vec::new(); 143 | for var in &vars_in_goals { 144 | line.push(format!("{} = {}", var, subst_bindings(solution.clone(), Term::Var(var.clone())))); 145 | } 146 | output.push(line.join(",")); 147 | } 148 | output.join(";\n") 149 | } else { 150 | format!("false.") 151 | } 152 | } 153 | 154 | fn top_level_prove_backtracking(mut goals: Vec, database: &Database) { 155 | let vars_in_goals = find_variables_in_goals(&goals); 156 | goals.push(Term::Atom("__backtracking?".into())); 157 | let solution = prove_all(goals.into(), Some(HashMap::new()), database, &vars_in_goals); 158 | 159 | if solution.is_some() { 160 | println!("true.") 161 | } else { 162 | println!("false.") 163 | } 164 | } 165 | 166 | fn ask_confirm() -> bool { 167 | loop { 168 | let mut input = String::new(); 169 | std::io::stdin().read_line(&mut input).unwrap(); 170 | match input.chars().nth(0).unwrap() { 171 | ';' => return true, 172 | _ => return false, 173 | } 174 | } 175 | } 176 | 177 | fn subst_bindings(bindings: Bindings, term: Term) -> Term { 178 | let bindings = bindings.expect("Only can be called when bindings are OK"); 179 | match term { 180 | Term::Atom(x) => Term::Atom(x.clone()), 181 | Term::Var(ref x) => { 182 | if let Some(value) = bindings.get(x) { 183 | subst_bindings(Some(bindings.clone()), value.clone()) 184 | } else { 185 | Term::Var(x.clone()) 186 | } 187 | } 188 | Term::Str(f, args) => { 189 | Term::Str(f.clone(), args.iter().map(|t| subst_bindings(Some(bindings.clone()), t.clone())).collect()) 190 | } 191 | } 192 | } 193 | 194 | pub fn find_variables_in_goals(goals: &Vec) -> HashSet { 195 | let mut vars = HashSet::new(); 196 | for goal in goals { 197 | match goal { 198 | Term::Var(var) => { 199 | vars.insert(var.clone()); 200 | }, 201 | Term::Atom(_) => (), 202 | Term::Str(_, args) => vars.extend(find_variables_in_goals(&args)) 203 | } 204 | } 205 | vars 206 | } 207 | 208 | #[test] 209 | fn socrates_test() { 210 | let mut db = Database::new(); 211 | let clause = Clause { 212 | head: Term::Str("mortal".into(), vec![Term::Var("X".into())]), 213 | body: vec![Term::Str("human".into(), vec![Term::Var("X".into())])], 214 | }; 215 | db.add_clause(clause); 216 | let clause = Clause { 217 | head: Term::Str("human".into(), vec![Term::Atom("socrates".into())]), 218 | body: vec![Term::Atom("true".into())], 219 | }; 220 | db.add_clause(clause); 221 | 222 | let clause = Clause { 223 | head: Term::Atom("true".into()), 224 | body: Vec::new(), 225 | }; 226 | db.add_clause(clause); 227 | 228 | let query1 = Term::Str("human".into(), vec![Term::Atom("socrates".into())]); 229 | let result = top_level_prove(vec![query1], &db); 230 | assert_eq!(result, ""); 231 | 232 | let query2 = Term::Str("mortal".into(), vec![Term::Atom("socrates".into())]); 233 | let result = top_level_prove(vec![query2], &db); 234 | assert_eq!(result, ""); 235 | 236 | let query3 = Term::Str("mortal".into(), vec![Term::Var("X".into())]); 237 | let result = top_level_prove(vec![query3], &db); 238 | assert_eq!(result, "X = socrates"); 239 | 240 | let query4 = Term::Str("mrtl".into(), vec![Term::Atom("socrates".into())]); 241 | let result = top_level_prove(vec![query4], &db); 242 | assert_eq!(result, "false."); 243 | 244 | let query5 = Term::Str("mortal".into(), vec![Term::Atom("gepeto".into())]); 245 | let result = top_level_prove(vec![query5], &db); 246 | assert_eq!(result, "false."); 247 | } 248 | 249 | #[test] 250 | fn likes() { 251 | let mut db = Database::new(); 252 | db.add_clause(Clause { 253 | head: Term::Str("likes".into(), vec![Term::Atom("kim".into()), Term::Atom("robin".into())]), 254 | body: vec![], 255 | }); 256 | db.add_clause(Clause { 257 | head: Term::Str("likes".into(), vec![Term::Atom("sandy".into()), Term::Atom("lee".into())]), 258 | body: vec![], 259 | }); 260 | db.add_clause(Clause { 261 | head: Term::Str("likes".into(), vec![Term::Atom("sandy".into()), Term::Atom("kim".into())]), 262 | body: vec![], 263 | }); 264 | db.add_clause(Clause { 265 | head: Term::Str("likes".into(), vec![Term::Atom("robin".into()), Term::Atom("cats".into())]), 266 | body: vec![], 267 | }); 268 | db.add_clause(Clause { 269 | head: Term::Str("likes".into(), vec![Term::Atom("sandy".into()), Term::Var("X".into())]), 270 | body: vec![Term::Str("likes".into(), vec![Term::Var("X".into()), Term::Atom("cats".into())])], 271 | }); 272 | db.add_clause(Clause { 273 | head: Term::Str("likes".into(), vec![Term::Atom("kim".into()), Term::Var("X".into())]), 274 | body: vec![ 275 | Term::Str("likes".into(), vec![Term::Var("X".into()), Term::Atom("lee".into())]), 276 | Term::Str("likes".into(), vec![Term::Var("X".into()), Term::Atom("kim".into())]) 277 | ], 278 | }); 279 | db.add_clause(Clause { 280 | head: Term::Str("likes".into(), vec![Term::Var("X".into()), Term::Var("X".into())]), 281 | body: vec![], 282 | }); 283 | 284 | let query1 = Term::Str("likes".into(), vec![Term::Atom("sandy".into()), Term::Var("Who".into())]); 285 | let result = top_level_prove(vec![query1], &db); 286 | assert_eq!(result, "Who = lee;\nWho = kim;\nWho = robin;\nWho = sandy;\nWho = cats;\nWho = sandy"); 287 | 288 | let query2 = Term::Str("likes".into(), vec![Term::Var("Who".into()), Term::Atom("sandy".into())]); 289 | let result = top_level_prove(vec![query2], &db); 290 | assert_eq!(result, "Who = sandy;\nWho = kim;\nWho = sandy"); 291 | 292 | let query3 = Term::Str("likes".into(), vec![Term::Atom("robin".into()), Term::Atom("lee".into())]); 293 | let result = top_level_prove(vec![query3], &db); 294 | assert_eq!(result, "false."); 295 | } 296 | 297 | /* 298 | #[test] 299 | fn backtracking() { 300 | let mut db = Database::new(); 301 | let clause = Clause { 302 | head: Term::Str("human".into(), vec![Term::Atom("socrates".into())]), 303 | body: vec![Term::Atom("true".into())], 304 | }; 305 | db.add_clause(clause); 306 | let clause = Clause { 307 | head: Term::Str("human".into(), vec![Term::Atom("plato".into())]), 308 | body: vec![Term::Atom("true".into())], 309 | }; 310 | db.add_clause(clause); 311 | let clause = Clause { 312 | head: Term::Str("human".into(), vec![Term::Atom("aristotle".into())]), 313 | body: vec![Term::Atom("true".into())], 314 | }; 315 | db.add_clause(clause); 316 | 317 | let clause = Clause { 318 | head: Term::Atom("true".into()), 319 | body: Vec::new(), 320 | }; 321 | db.add_clause(clause); 322 | 323 | let query1 = Term::Str("human".into(), vec![Term::Var("X".into())]); 324 | top_level_prove_backtracking(vec![query1], &db); 325 | }*/ 326 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | EUROPEAN UNION PUBLIC LICENCE v. 1.2 2 | EUPL © the European Union 2007, 2016 3 | 4 | This European Union Public Licence (the ‘EUPL’) applies to the Work (as 5 | defined below) which is provided under the terms of this Licence. Any use of 6 | the Work, other than as authorised under this Licence is prohibited (to the 7 | extent such use is covered by a right of the copyright holder of the Work). 8 | 9 | The Work is provided under the terms of this Licence when the Licensor (as 10 | defined below) has placed the following notice immediately following the 11 | copyright notice for the Work: 12 | 13 | Licensed under the EUPL 14 | 15 | or has expressed by any other means his willingness to license under the EUPL. 16 | 17 | 1. Definitions 18 | 19 | In this Licence, the following terms have the following meaning: 20 | 21 | - ‘The Licence’: this Licence. 22 | 23 | - ‘The Original Work’: the work or software distributed or communicated by the 24 | Licensor under this Licence, available as Source Code and also as Executable 25 | Code as the case may be. 26 | 27 | - ‘Derivative Works’: the works or software that could be created by the 28 | Licensee, based upon the Original Work or modifications thereof. This 29 | Licence does not define the extent of modification or dependence on the 30 | Original Work required in order to classify a work as a Derivative Work; 31 | this extent is determined by copyright law applicable in the country 32 | mentioned in Article 15. 33 | 34 | - ‘The Work’: the Original Work or its Derivative Works. 35 | 36 | - ‘The Source Code’: the human-readable form of the Work which is the most 37 | convenient for people to study and modify. 38 | 39 | - ‘The Executable Code’: any code which has generally been compiled and which 40 | is meant to be interpreted by a computer as a program. 41 | 42 | - ‘The Licensor’: the natural or legal person that distributes or communicates 43 | the Work under the Licence. 44 | 45 | - ‘Contributor(s)’: any natural or legal person who modifies the Work under 46 | the Licence, or otherwise contributes to the creation of a Derivative Work. 47 | 48 | - ‘The Licensee’ or ‘You’: any natural or legal person who makes any usage of 49 | the Work under the terms of the Licence. 50 | 51 | - ‘Distribution’ or ‘Communication’: any act of selling, giving, lending, 52 | renting, distributing, communicating, transmitting, or otherwise making 53 | available, online or offline, copies of the Work or providing access to its 54 | essential functionalities at the disposal of any other natural or legal 55 | person. 56 | 57 | 2. Scope of the rights granted by the Licence 58 | 59 | The Licensor hereby grants You a worldwide, royalty-free, non-exclusive, 60 | sublicensable licence to do the following, for the duration of copyright 61 | vested in the Original Work: 62 | 63 | - use the Work in any circumstance and for all usage, 64 | - reproduce the Work, 65 | - modify the Work, and make Derivative Works based upon the Work, 66 | - communicate to the public, including the right to make available or display 67 | the Work or copies thereof to the public and perform publicly, as the case 68 | may be, the Work, 69 | - distribute the Work or copies thereof, 70 | - lend and rent the Work or copies thereof, 71 | - sublicense rights in the Work or copies thereof. 72 | 73 | Those rights can be exercised on any media, supports and formats, whether now 74 | known or later invented, as far as the applicable law permits so. 75 | 76 | In the countries where moral rights apply, the Licensor waives his right to 77 | exercise his moral right to the extent allowed by law in order to make 78 | effective the licence of the economic rights here above listed. 79 | 80 | The Licensor grants to the Licensee royalty-free, non-exclusive usage rights 81 | to any patents held by the Licensor, to the extent necessary to make use of 82 | the rights granted on the Work under this Licence. 83 | 84 | 3. Communication of the Source Code 85 | 86 | The Licensor may provide the Work either in its Source Code form, or as 87 | Executable Code. If the Work is provided as Executable Code, the Licensor 88 | provides in addition a machine-readable copy of the Source Code of the Work 89 | along with each copy of the Work that the Licensor distributes or indicates, 90 | in a notice following the copyright notice attached to the Work, a repository 91 | where the Source Code is easily and freely accessible for as long as the 92 | Licensor continues to distribute or communicate the Work. 93 | 94 | 4. Limitations on copyright 95 | 96 | Nothing in this Licence is intended to deprive the Licensee of the benefits 97 | from any exception or limitation to the exclusive rights of the rights owners 98 | in the Work, of the exhaustion of those rights or of other applicable 99 | limitations thereto. 100 | 101 | 5. Obligations of the Licensee 102 | 103 | The grant of the rights mentioned above is subject to some restrictions and 104 | obligations imposed on the Licensee. Those obligations are the following: 105 | 106 | Attribution right: The Licensee shall keep intact all copyright, patent or 107 | trademarks notices and all notices that refer to the Licence and to the 108 | disclaimer of warranties. The Licensee must include a copy of such notices and 109 | a copy of the Licence with every copy of the Work he/she distributes or 110 | communicates. The Licensee must cause any Derivative Work to carry prominent 111 | notices stating that the Work has been modified and the date of modification. 112 | 113 | Copyleft clause: If the Licensee distributes or communicates copies of the 114 | Original Works or Derivative Works, this Distribution or Communication will be 115 | done under the terms of this Licence or of a later version of this Licence 116 | unless the Original Work is expressly distributed only under this version of 117 | the Licence — for example by communicating ‘EUPL v. 1.2 only’. The Licensee 118 | (becoming Licensor) cannot offer or impose any additional terms or conditions 119 | on the Work or Derivative Work that alter or restrict the terms of the 120 | Licence. 121 | 122 | Compatibility clause: If the Licensee Distributes or Communicates Derivative 123 | Works or copies thereof based upon both the Work and another work licensed 124 | under a Compatible Licence, this Distribution or Communication can be done 125 | under the terms of this Compatible Licence. For the sake of this clause, 126 | ‘Compatible Licence’ refers to the licences listed in the appendix attached to 127 | this Licence. Should the Licensee's obligations under the Compatible Licence 128 | conflict with his/her obligations under this Licence, the obligations of the 129 | Compatible Licence shall prevail. 130 | 131 | Provision of Source Code: When distributing or communicating copies of the 132 | Work, the Licensee will provide a machine-readable copy of the Source Code or 133 | indicate a repository where this Source will be easily and freely available 134 | for as long as the Licensee continues to distribute or communicate the Work. 135 | 136 | Legal Protection: This Licence does not grant permission to use the trade 137 | names, trademarks, service marks, or names of the Licensor, except as required 138 | for reasonable and customary use in describing the origin of the Work and 139 | reproducing the content of the copyright notice. 140 | 141 | 6. Chain of Authorship 142 | 143 | The original Licensor warrants that the copyright in the Original Work granted 144 | hereunder is owned by him/her or licensed to him/her and that he/she has the 145 | power and authority to grant the Licence. 146 | 147 | Each Contributor warrants that the copyright in the modifications he/she 148 | brings to the Work are owned by him/her or licensed to him/her and that he/she 149 | has the power and authority to grant the Licence. 150 | 151 | Each time You accept the Licence, the original Licensor and subsequent 152 | Contributors grant You a licence to their contributions to the Work, under the 153 | terms of this Licence. 154 | 155 | 7. Disclaimer of Warranty 156 | 157 | The Work is a work in progress, which is continuously improved by numerous 158 | Contributors. It is not a finished work and may therefore contain defects or 159 | ‘bugs’ inherent to this type of development. 160 | 161 | For the above reason, the Work is provided under the Licence on an ‘as is’ 162 | basis and without warranties of any kind concerning the Work, including 163 | without limitation merchantability, fitness for a particular purpose, absence 164 | of defects or errors, accuracy, non-infringement of intellectual property 165 | rights other than copyright as stated in Article 6 of this Licence. 166 | 167 | This disclaimer of warranty is an essential part of the Licence and a 168 | condition for the grant of any rights to the Work. 169 | 170 | 8. Disclaimer of Liability 171 | 172 | Except in the cases of wilful misconduct or damages directly caused to natural 173 | persons, the Licensor will in no event be liable for any direct or indirect, 174 | material or moral, damages of any kind, arising out of the Licence or of the 175 | use of the Work, including without limitation, damages for loss of goodwill, 176 | work stoppage, computer failure or malfunction, loss of data or any commercial 177 | damage, even if the Licensor has been advised of the possibility of such 178 | damage. However, the Licensor will be liable under statutory product liability 179 | laws as far such laws apply to the Work. 180 | 181 | 9. Additional agreements 182 | 183 | While distributing the Work, You may choose to conclude an additional 184 | agreement, defining obligations or services consistent with this Licence. 185 | However, if accepting obligations, You may act only on your own behalf and on 186 | your sole responsibility, not on behalf of the original Licensor or any other 187 | Contributor, and only if You agree to indemnify, defend, and hold each 188 | Contributor harmless for any liability incurred by, or claims asserted against 189 | such Contributor by the fact You have accepted any warranty or additional 190 | liability. 191 | 192 | 10. Acceptance of the Licence 193 | 194 | The provisions of this Licence can be accepted by clicking on an icon ‘I 195 | agree’ placed under the bottom of a window displaying the text of this Licence 196 | or by affirming consent in any other similar way, in accordance with the rules 197 | of applicable law. Clicking on that icon indicates your clear and irrevocable 198 | acceptance of this Licence and all of its terms and conditions. 199 | 200 | Similarly, you irrevocably accept this Licence and all of its terms and 201 | conditions by exercising any rights granted to You by Article 2 of this 202 | Licence, such as the use of the Work, the creation by You of a Derivative Work 203 | or the Distribution or Communication by You of the Work or copies thereof. 204 | 205 | 11. Information to the public 206 | 207 | In case of any Distribution or Communication of the Work by means of 208 | electronic communication by You (for example, by offering to download the Work 209 | from a remote location) the distribution channel or media (for example, a 210 | website) must at least provide to the public the information requested by the 211 | applicable law regarding the Licensor, the Licence and the way it may be 212 | accessible, concluded, stored and reproduced by the Licensee. 213 | 214 | 12. Termination of the Licence 215 | 216 | The Licence and the rights granted hereunder will terminate automatically upon 217 | any breach by the Licensee of the terms of the Licence. 218 | 219 | Such a termination will not terminate the licences of any person who has 220 | received the Work from the Licensee under the Licence, provided such persons 221 | remain in full compliance with the Licence. 222 | 223 | 13. Miscellaneous 224 | 225 | Without prejudice of Article 9 above, the Licence represents the complete 226 | agreement between the Parties as to the Work. 227 | 228 | If any provision of the Licence is invalid or unenforceable under applicable 229 | law, this will not affect the validity or enforceability of the Licence as a 230 | whole. Such provision will be construed or reformed so as necessary to make it 231 | valid and enforceable. 232 | 233 | The European Commission may publish other linguistic versions or new versions 234 | of this Licence or updated versions of the Appendix, so far this is required 235 | and reasonable, without reducing the scope of the rights granted by the 236 | Licence. New versions of the Licence will be published with a unique version 237 | number. 238 | 239 | All linguistic versions of this Licence, approved by the European Commission, 240 | have identical value. Parties can take advantage of the linguistic version of 241 | their choice. 242 | 243 | 14. Jurisdiction 244 | 245 | Without prejudice to specific agreement between parties, 246 | 247 | - any litigation resulting from the interpretation of this License, arising 248 | between the European Union institutions, bodies, offices or agencies, as a 249 | Licensor, and any Licensee, will be subject to the jurisdiction of the Court 250 | of Justice of the European Union, as laid down in article 272 of the Treaty 251 | on the Functioning of the European Union, 252 | 253 | - any litigation arising between other parties and resulting from the 254 | interpretation of this License, will be subject to the exclusive 255 | jurisdiction of the competent court where the Licensor resides or conducts 256 | its primary business. 257 | 258 | 15. Applicable Law 259 | 260 | Without prejudice to specific agreement between parties, 261 | 262 | - this Licence shall be governed by the law of the European Union Member State 263 | where the Licensor has his seat, resides or has his registered office, 264 | 265 | - this licence shall be governed by Belgian law if the Licensor has no seat, 266 | residence or registered office inside a European Union Member State. 267 | 268 | Appendix 269 | 270 | ‘Compatible Licences’ according to Article 5 EUPL are: 271 | 272 | - GNU General Public License (GPL) v. 2, v. 3 273 | - GNU Affero General Public License (AGPL) v. 3 274 | - Open Software License (OSL) v. 2.1, v. 3.0 275 | - Eclipse Public License (EPL) v. 1.0 276 | - CeCILL v. 2.0, v. 2.1 277 | - Mozilla Public Licence (MPL) v. 2 278 | - GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3 279 | - Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for 280 | works other than software 281 | - European Union Public Licence (EUPL) v. 1.1, v. 1.2 282 | - Québec Free and Open-Source Licence — Reciprocity (LiLiQ-R) or Strong 283 | Reciprocity (LiLiQ-R+). 284 | 285 | The European Commission may update this Appendix to later versions of the 286 | above licences without producing a new version of the EUPL, as long as they 287 | provide the rights granted in Article 2 of this Licence and protect the 288 | covered Source Code from exclusive appropriation. 289 | 290 | All other changes or additions to this Appendix require the production of a 291 | new EUPL version. 292 | --------------------------------------------------------------------------------