From fe4cb4834290c249d4c5f73b0a8f21e2b32abb9b Mon Sep 17 00:00:00 2001
From: aalbert <augustin.albert@bleu-azure.fr>
Date: Mon, 17 Oct 2022 19:16:49 +0200
Subject: [PATCH] =?UTF-8?q?feat(parser):=20Add=20new=20syntaxic=20sugar=20?=
 =?UTF-8?q?and=20meaningfull=20error=20messages,=20add=20more...=20?=
 =?UTF-8?q?=E2=9C=A8=EF=B8=8F=20Closes=20#10=20=F0=9F=90=94=EF=B8=8F?=
 =?UTF-8?q?=F0=9F=91=8D=EF=B8=8F=20Approved-by:=20v-lafeychine=20<vincent.?=
 =?UTF-8?q?lafeychine@proton.me>=20Approved-by:=20aalbert=20<augustin.albe?=
 =?UTF-8?q?rt@bleu-azure.fr>=20Approved-by:=20loutr=20<loutr@crans.org>?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

🦀️🍰🦀️🍰🦀️🍰

* fix(parser): Fix the semantics of products without parentheses.

* chore(parser): renaming

* chore(parser & proost): renaming

* feat(parser): documentation

* fix(parser): resolve threads

* feat(parser): Add new syntaxic sugar and meaningfull error messages, add more efficient conversion into terms (VecDeque), remove unused struct ClassicTerm, remove useless & outdated test.
feat(kernel): Add new command: DefineCheckType. eg: "def x:P := P"
feat(proost): Process multiples commands in one line

* feat(parser): Add new syntaxic sugar and meaningfull error messages, add more efficient conversion into terms (VecDeque), remove unused struct ClassicTerm, remove useless & outdated test.
feat(kernel): Add new command: DefineCheckType. eg: "def x:P := P"
feat(proost): Process multiples commands in one line
---
 kernel/src/command.rs        |   3 +
 parser/src/classic_term.pest |  20 ----
 parser/src/classic_term.rs   |  65 -------------
 parser/src/lib.rs            |   7 +-
 parser/src/parser.rs         | 175 ++++++++++++++++++++---------------
 parser/src/term.pest         |  40 ++++++++
 parser/tests/example.mdln    |   4 -
 parser/tests/example.rs      |  12 ---
 proost/src/main.rs           |  15 ++-
 9 files changed, 159 insertions(+), 182 deletions(-)
 delete mode 100644 parser/src/classic_term.pest
 delete mode 100644 parser/src/classic_term.rs
 create mode 100644 parser/src/term.pest
 delete mode 100644 parser/tests/example.mdln
 delete mode 100644 parser/tests/example.rs

diff --git a/kernel/src/command.rs b/kernel/src/command.rs
index 486b44ac..d8d3afff 100644
--- a/kernel/src/command.rs
+++ b/kernel/src/command.rs
@@ -11,4 +11,7 @@ pub enum Command {
 
     #[display(fmt = "Type {}.", _0)]
     GetType(Term),
+
+    #[display(fmt = "Define {} : {} := {}.", _0, _1, _2)]
+    DefineCheckType(String, Term, Term),
 }
diff --git a/parser/src/classic_term.pest b/parser/src/classic_term.pest
deleted file mode 100644
index 9679f523..00000000
--- a/parser/src/classic_term.pest
+++ /dev/null
@@ -1,20 +0,0 @@
-WHITESPACE = _{ " " | "\t" }
-COMMENT = _{ "#" ~ (!"\n" ~ ANY)* }
-number = @{ ASCII_NONZERO_DIGIT ~ ASCII_DIGIT* }
-string = @{ ASCII_ALPHA ~ ASCII_ALPHANUMERIC* }
-
-Term = _{ Prop | Type | Var | App | Abs | Prod | ParTerm }
-Prop = { "P" }
-Type = { "T(" ~ number ~ ")" }
-Var = { string }
-App = { "(" ~ ( Var | Abs | Prod ) ~ ")" ~ Term }
-Abs = { "/\\" ~ string ~ ":" ~ Term ~ "->" ~ Term }
-Prod = { "\\/" ~ string ~ ":" ~ Term ~ "->" ~ Term }
-ParTerm = _{ "(" ~ Term ~ ")" }
-
-Command = _{ ( Define | CheckType | GetType ) ~ "." }
-Define = { ( "define" | "Define" ) ~ string ~ ":=" ~ Term }
-CheckType = { ( "CheckType" | "Check" | "check" ) ~  Term ~ ":" ~ Term }
-GetType = { ( "Type" | "GetType" | "type" ) ~ Term }
-
-File = { SOI ~ (NEWLINE* ~ Command)* ~ NEWLINE* ~ EOI }
diff --git a/parser/src/classic_term.rs b/parser/src/classic_term.rs
deleted file mode 100644
index 658e8c05..00000000
--- a/parser/src/classic_term.rs
+++ /dev/null
@@ -1,65 +0,0 @@
-use kernel::derive_more::Display;
-use kernel::num_bigint::BigUint;
-use kernel::Term;
-
-#[derive(Clone, Debug, Display)]
-pub enum ClassicTerm {
-    #[display(fmt = "{}", _0)]
-    Var(String),
-
-    #[display(fmt = "\u{02119}")]
-    Prop,
-
-    #[display(fmt = "Type({})", _0)]
-    Type(BigUint),
-
-    #[display(fmt = "({} {})", _0, _1)]
-    App(Box<ClassicTerm>, Box<ClassicTerm>),
-
-    #[display(fmt = "\u{003BB}{} \u{02192} {}", _0, _1)]
-    Abs(String, Box<ClassicTerm>, Box<ClassicTerm>),
-
-    #[display(fmt = "\u{02200}{} \u{02192} {}", _0, _1)]
-    Prod(String, Box<ClassicTerm>, Box<ClassicTerm>),
-}
-
-fn try_from_assign(term: ClassicTerm, known_ids: &mut Vec<String>) -> Result<Term, String> {
-    match term {
-        ClassicTerm::Prop => Ok(Term::Prop),
-        ClassicTerm::Type(i) => Ok(Term::Type(i.into())),
-        ClassicTerm::App(t1, t2) => {
-            let t1 = box try_from_assign(*t1, known_ids)?;
-            let t2 = box try_from_assign(*t2, known_ids)?;
-
-            Ok(Term::App(t1, t2))
-        }
-        ClassicTerm::Var(s) => match known_ids.iter().rev().position(|r| *r == s) {
-            Some(i) => Ok(Term::Var((i + 1).into())),
-            None => Err(s),
-        },
-        ClassicTerm::Abs(s, t1, t2) => {
-            known_ids.push(s);
-            let t1 = box try_from_assign(*t1, known_ids)?;
-            let t2 = box try_from_assign(*t2, known_ids)?;
-            known_ids.pop();
-
-            Ok(Term::Abs(t1, t2))
-        }
-        ClassicTerm::Prod(s, t1, t2) => {
-            known_ids.push(s);
-            let t1 = box try_from_assign(*t1, known_ids)?;
-            let t2 = box try_from_assign(*t2, known_ids)?;
-            known_ids.pop();
-
-            Ok(Term::Prod(t1, t2))
-        }
-    }
-}
-
-impl TryFrom<ClassicTerm> for Term {
-    type Error = String;
-
-    fn try_from(t: ClassicTerm) -> Result<Self, Self::Error> {
-        try_from_assign(t, &mut Vec::new())
-    }
-}
diff --git a/parser/src/lib.rs b/parser/src/lib.rs
index 664c084b..20c7f747 100644
--- a/parser/src/lib.rs
+++ b/parser/src/lib.rs
@@ -1,9 +1,12 @@
+//! Fast parser for λ-terms and commands using pest.
+//!
+//! Provides the function `parse_commands` to parse both files and single commands.
+
 #![feature(box_syntax)]
 
 #[macro_use]
 extern crate pest_derive;
 
-mod classic_term;
 mod parser;
 
-pub use self::parser::{parse_command, parse_file, parse_term};
+pub use self::parser::parse_commands;
diff --git a/parser/src/parser.rs b/parser/src/parser.rs
index d23ace61..7db5ede1 100644
--- a/parser/src/parser.rs
+++ b/parser/src/parser.rs
@@ -1,112 +1,139 @@
-use crate::classic_term::ClassicTerm;
+use kernel::num_bigint::BigUint;
 use kernel::{Command, Term};
 use pest::error::{Error, ErrorVariant};
 use pest::iterators::Pair;
-use pest::{Parser, Position};
+use pest::Parser;
+use std::collections::VecDeque;
 
 #[derive(Parser)]
-#[grammar = "classic_term.pest"]
+#[grammar = "term.pest"]
 struct CommandParser;
 
-fn build_classic_term_from_expr(pair: Pair<Rule>) -> ClassicTerm {
+fn build_term_from_expr(
+    pair: Pair<Rule>,
+    known_vars: &mut VecDeque<String>,
+    //defined_vars: issue #18 TODO use a hash map of known variables
+) -> Result<Term, Box<Error<Rule>>> {
     match pair.as_rule() {
-        Rule::Prop => ClassicTerm::Prop,
-        Rule::Var => ClassicTerm::Var(pair.into_inner().as_str().to_string()),
-        Rule::Type => ClassicTerm::Type(pair.into_inner().as_str().parse().unwrap()),
+        Rule::Prop => Ok(Term::Prop),
+        Rule::Type => Ok(Term::Type(
+            pair.into_inner()
+                .as_str()
+                .parse::<BigUint>()
+                .unwrap()
+                .into(),
+        )),
+
+        Rule::Var => {
+            //issue #18 TODO use a hash map of known variables
+            let span = pair.as_span();
+            let name = pair.into_inner().as_str().to_string();
+            match known_vars.iter().position(|x| *x == name) {
+                Some(i) => Ok(Term::Var((i + 1).into())),
+                None => Err(box Error::new_from_span(
+                    ErrorVariant::CustomError {
+                        message: String::from("free variable: ") + &name,
+                    },
+                    span,
+                )),
+            }
+        }
+        Rule::Prod => {
+            let mut iter = pair
+                .into_inner()
+                .map(|x| build_term_from_expr(x, known_vars))
+                .rev();
+            let t = iter.next().unwrap()?;
+            iter.try_fold(t, |acc, x| Ok(Term::Prod(box x?, box acc)))
+        }
         Rule::App => {
-            let mut iter = pair.into_inner();
-            let t1 = build_classic_term_from_expr(iter.next().unwrap());
-            let t2 = build_classic_term_from_expr(iter.next().unwrap());
-            ClassicTerm::App(box t1, box t2)
+            let mut iter = pair
+                .into_inner()
+                .map(|x| build_term_from_expr(x, known_vars));
+            let t = iter.next().unwrap()?;
+            iter.try_fold(t, |acc, x| Ok(Term::App(box acc, box x?)))
         }
         Rule::Abs => {
-            let mut iter = pair.into_inner();
-            let s = iter.next().unwrap().as_str().to_string();
-            let t1 = build_classic_term_from_expr(iter.next().unwrap());
-            let t2 = build_classic_term_from_expr(iter.next().unwrap());
-            ClassicTerm::Abs(s, box t1, box t2)
+            let iter = pair.into_inner();
+            let mut iter2 = iter.clone().rev();
+            for pair in iter {
+                if pair.as_rule() == Rule::arg_abs {
+                    let name = pair.into_inner().next().unwrap().as_str().to_string();
+                    known_vars.push_front(name);
+                }
+            }
+            let t = build_term_from_expr(iter2.next().unwrap(), known_vars)?;
+            iter2.try_fold(t, |acc, x| {
+                known_vars.pop_front();
+                let mut iter3 = x.into_inner();
+                iter3.next();
+                let t = build_term_from_expr(iter3.next().unwrap(), known_vars)?;
+                Ok(Term::Abs(box t, box acc))
+            })
         }
-        Rule::Prod => {
-            let mut iter = pair.into_inner();
-            let s = iter.next().unwrap().as_str().to_string();
-            let t1 = build_classic_term_from_expr(iter.next().unwrap());
-            let t2 = build_classic_term_from_expr(iter.next().unwrap());
-            ClassicTerm::Prod(s, box t1, box t2)
+        Rule::dProd => {
+            let iter = pair.into_inner();
+            let mut iter2 = iter.clone().rev();
+            for pair in iter {
+                if pair.as_rule() == Rule::arg_dprod {
+                    let name = pair.into_inner().next().unwrap().as_str().to_string();
+                    known_vars.push_front(name);
+                }
+            }
+            let t = build_term_from_expr(iter2.next().unwrap(), known_vars)?;
+            iter2.try_fold(t, |acc, x| {
+                known_vars.pop_front();
+                let mut iter3 = x.into_inner();
+                iter3.next();
+                let t = build_term_from_expr(iter3.next().unwrap(), known_vars)?;
+                Ok(Term::Prod(box t, box acc))
+            })
         }
         term => panic!("Unexpected term: {:?}", term),
     }
 }
 
-fn build_term_from_expr(pair: Pair<Rule>) -> Result<Term, String> {
-    Term::try_from(build_classic_term_from_expr(pair))
-}
-
-fn build_command_from_expr(pair: Pair<Rule>) -> Result<Command, String> {
+fn build_command_from_expr(pair: Pair<Rule>) -> Result<Command, Box<Error<Rule>>> {
     match pair.as_rule() {
         Rule::GetType => {
             let mut iter = pair.into_inner();
-            let t = build_term_from_expr(iter.next().unwrap())?;
+            let t = build_term_from_expr(iter.next().unwrap(), &mut VecDeque::new())?;
             Ok(Command::GetType(t))
         }
         Rule::CheckType => {
             let mut iter = pair.into_inner();
-            let t1 = build_term_from_expr(iter.next().unwrap())?;
-            let t2 = build_term_from_expr(iter.next().unwrap())?;
+            let t1 = build_term_from_expr(iter.next().unwrap(), &mut VecDeque::new())?;
+            let t2 = build_term_from_expr(iter.next().unwrap(), &mut VecDeque::new())?;
             Ok(Command::CheckType(t1, t2))
         }
         Rule::Define => {
             let mut iter = pair.into_inner();
             let s = iter.next().unwrap().as_str().to_string();
-            let t = build_term_from_expr(iter.next().unwrap())?;
+            let t = build_term_from_expr(iter.next().unwrap(), &mut VecDeque::new())?;
             Ok(Command::Define(s, t))
         }
-        command => panic!("Unexpected command: {:?}", command),
-    }
-}
-
-pub fn parse_term(file: &str) -> Result<Term, Box<Error<Rule>>> {
-    let pair = CommandParser::parse(Rule::Term, file)?.next().unwrap();
-    match build_term_from_expr(pair) {
-        Ok(t) => Ok(t),
-        Err(s) => Err(box Error::new_from_pos(
-            ErrorVariant::CustomError {
-                message: String::from("Free variable: ") + &s,
-            },
-            //TODO: Add more details, eg: variable position (#8)
-            Position::from_start(file),
-        )),
-    }
-}
-
-pub fn parse_command(file: &str) -> Result<Command, Box<Error<Rule>>> {
-    let pair = CommandParser::parse(Rule::Command, file)?.next().unwrap();
-    match build_command_from_expr(pair) {
-        Ok(t) => Ok(t),
-        Err(s) => Err(box Error::new_from_pos(
+        Rule::DefineCheckType => {
+            let mut iter = pair.into_inner();
+            let s = iter.next().unwrap().as_str().to_string();
+            let t1 = build_term_from_expr(iter.next().unwrap(), &mut VecDeque::new())?;
+            let t2 = build_term_from_expr(iter.next().unwrap(), &mut VecDeque::new())?;
+            Ok(Command::DefineCheckType(s, t1, t2))
+        }
+        Rule::error_dot => Err(box Error::new_from_pos(
             ErrorVariant::CustomError {
-                message: String::from("Free variable: ") + &s,
+                message: String::from("missing dot"),
             },
-            Position::from_start(file),
+            pair.as_span().end_pos(),
         )),
+        command => panic!("Unexpected command: {:?}", command),
     }
 }
 
-// TODO: Unsatisfactory behavior (#7)
-pub fn parse_file(file: &str) -> Result<Vec<Command>, Box<Error<Rule>>> {
-    let mut vec = Vec::new();
-    for pair in CommandParser::parse(Rule::File, file)?
-        .next()
-        .unwrap()
-        .into_inner()
-    {
-        match pair.as_rule() {
-            Rule::EOI => (),
-            _ => {
-                if let Ok(t) = build_command_from_expr(pair) {
-                    vec.push(t)
-                }
-            }
-        }
-    }
-    Ok(vec)
+/// Parse a text input and try to convert it into a vector of commands.
+///
+/// If unsuccessful, a box containing the first error that was encountered is returned.
+pub fn parse_commands(file: &str) -> Result<Vec<Command>, Box<Error<Rule>>> {
+    CommandParser::parse(Rule::File, file)?
+        .map(build_command_from_expr)
+        .collect()
 }
diff --git a/parser/src/term.pest b/parser/src/term.pest
new file mode 100644
index 00000000..2c7a5c12
--- /dev/null
+++ b/parser/src/term.pest
@@ -0,0 +1,40 @@
+WHITESPACE = _{ WHITE_SPACE }
+COMMENT = _{ "#" ~ (!"\n" ~ ANY)* }
+number = @{ ASCII_DIGIT+ }
+string = @{ !keywords ~ ASCII_ALPHA ~ ( "_" | ASCII_ALPHANUMERIC )* }
+keywords = @{ ( "fun" | "type" | "def" | "check" | "Prop" | "Type" ) ~ !ASCII_ALPHANUMERIC }
+eoi = _{ !ANY }
+
+Term = _{ Abs | dProd | Prod | App | Var | Prop | Type | "(" ~ Term ~ ")" }
+term_prod = _{ App | Abs | dProd | Var | Prop | Type | "(" ~ term_prod ~ ")" }
+term_app = _{ Abs | dProd | Var | Prop | Type | "(" ~ Prod ~ ")" | "(" ~ term_app ~ ")" } 
+term_dprod = _{ App | Abs | dProd | Var | Prop | Type | "(" ~ Prod ~ ")" | "(" ~ term_dprod ~ ")" }
+term_abs = _{ App | Abs | dProd | Var | Prop | Type  | "(" ~ Prod ~ ")" | "(" ~ term_abs ~ ")" }
+
+Abs = { ( "fun" ~ ( arg_abs_par ~ ( "," ~ arg_abs_par )* ) ~ "=>" ~ Term ) }
+Abs_ = { ( "fun" ~ ( string ~ ( "," ~ string )* ) ~ "=>" ~ Term ) }
+arg_abs = { ( string ~ ":" ~ term_abs ) }
+arg_abs_par = _{ arg_abs | "(" ~ arg_abs ~ ")" }
+
+dProd = { "(" ~ ( arg_dprod_par ~ ( "," ~ arg_dprod_par )* ) ~ ")" ~ "->" ~ Term }
+dProd_ = { "(" ~ ( string ~ ( "," ~ string )* ) ~ ")" ~ "->" ~ Term }
+arg_dprod = { ( string ~ ":" ~ term_dprod ) }
+arg_dprod_par = _{ arg_dprod | "(" ~ arg_dprod ~ ")" }
+
+App = { term_app ~ term_app+ }
+Prod = { term_prod ~ ( "->" ~ term_prod )+ }
+
+Prop = { "Prop" }
+Type = { "Type(" ~ number ~ ")" }
+Var = { string }
+
+Command = _{ ( Define | CheckType | GetType | DefineCheckType ) ~ "." }
+Define = { "def" ~ string ~ ":=" ~ Term }
+DefineCheckType = { "def" ~ string ~ ":" ~ Term ~ ":=" ~ Term }
+CheckType = { "check" ~ Term ~ ":" ~ Term }
+GetType = { "type" ~ Term }
+
+Error = _{ error_dot }
+error_dot = { ( Define | CheckType | GetType | DefineCheckType ) }
+
+File = _{ SOI ~ ( NEWLINE* ~ ( Command | Error ) )* ~ NEWLINE* ~ eoi }
diff --git a/parser/tests/example.mdln b/parser/tests/example.mdln
deleted file mode 100644
index 69e17671..00000000
--- a/parser/tests/example.mdln
+++ /dev/null
@@ -1,4 +0,0 @@
-Define Test := P.
-
-
-
diff --git a/parser/tests/example.rs b/parser/tests/example.rs
deleted file mode 100644
index 0ee5ead7..00000000
--- a/parser/tests/example.rs
+++ /dev/null
@@ -1,12 +0,0 @@
-use kernel::{Command, Term};
-
-#[test]
-fn parse() {
-    use std::fs;
-    let contents: &str =
-        &fs::read_to_string("tests/example.mdln").expect("Should have been able to read the file");
-    assert_eq!(
-        parser::parse_file(contents),
-        Ok(vec![Command::Define("Test".to_string(), Term::Prop)])
-    );
-}
diff --git a/proost/src/main.rs b/proost/src/main.rs
index a97b027a..dbc7cd69 100644
--- a/proost/src/main.rs
+++ b/proost/src/main.rs
@@ -1,7 +1,7 @@
 #![feature(box_syntax)]
 
 use clap::Parser;
-use parser::{parse_command, parse_file};
+use parser::parse_commands;
 use rustyline::error::ReadlineError;
 use rustyline::Editor;
 use std::error::Error;
@@ -25,7 +25,7 @@ fn main() -> Result<(), Box<dyn Error>> {
         for path in args.files.iter() {
             match fs::read_to_string(path) {
                 Ok(contents) => {
-                    let _ = parse_file(&contents);
+                    let _ = parse_commands(&contents);
                 }
                 Err(_) => {
                     println!("Error: No such file or directory: {}", path);
@@ -46,13 +46,18 @@ fn main() -> Result<(), Box<dyn Error>> {
     loop {
         let readline = rl.readline(">> ");
         match readline {
-            Ok(line) => {
+            Ok(line) if !line.is_empty() => {
                 rl.add_history_entry(line.as_str());
-                match parse_command(line.as_str()) {
-                    Ok(command) => println!("{}", command),
+                match parse_commands(line.as_str()) {
+                    Ok(commands) => {
+                        for command in commands {
+                            println!("{}", command);
+                        }
+                    }
                     Err(err) => println!("{}", *err),
                 }
             }
+            Ok(_) => (),
             Err(ReadlineError::Interrupted) => {}
             Err(ReadlineError::Eof) => break,
             Err(err) => {
-- 
GitLab