Skip to content

Commit

Permalink
parser debug (#5)
Browse files Browse the repository at this point in the history
* disallow trailing characters on keywords; avoid unnecessary attempts

I was previously forcing the presence of ' ' after keywords. now we
ensure the absence of alpha_num after them. this seems tidier.

previously we could encounter weird behavior like `(if 1)` parsing to a
function application with variable named `if`. this is because we would
backtrack if `if` failed to parse, and end up in the `App` branch. now I
avoid backtracking by committing (no `attempt`). in order todo so I had
to factor out the common prefix of lam & let ("l"). not sure if there's
a better way.

* cargo fmt

* parse: restrict `Name`: disallow keywords

* fix tests & revert `attempt`

avoid generating reserved keywords for `Name`, as we now disallow that.

removing the `attempt`s caused us to fail on things like `(ifio 1)`
which is a valid application of a variable `ifio`. since we were
refusing to backtrack out of the If route, we weren't handling it.
  • Loading branch information
mhuesch authored Oct 31, 2020
1 parent 33f8e8d commit e78f512
Show file tree
Hide file tree
Showing 5 changed files with 57 additions and 29 deletions.
8 changes: 6 additions & 2 deletions src/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -330,8 +330,12 @@ fn unify_many(mut ts_1: Vec<Type>, mut ts_2: Vec<Type>) -> Result<Subst, TypeErr
(None, None) => Ok(HashMap::new()),
(Some(t1), Some(t2)) => {
let subst_1 = unifies(t1, t2)?;
for t in ts_1.iter_mut() { *t = t.clone().apply(&subst_1) }
for t in ts_2.iter_mut() { *t = t.clone().apply(&subst_1) }
for t in ts_1.iter_mut() {
*t = t.clone().apply(&subst_1)
}
for t in ts_2.iter_mut() {
*t = t.clone().apply(&subst_1)
}
let subst_2 = unify_many(ts_1, ts_2)?;
Ok(compose(subst_2, subst_1))
}
Expand Down
54 changes: 35 additions & 19 deletions src/parse.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use combine::error::ParseError;
use combine::parser::char::{char, digit, letter, spaces, string};
use combine::stream::Stream;
use combine::error::{ParseError, StreamError};
use combine::parser::char::{alpha_num, char, digit, letter, spaces, string};
use combine::stream::{Stream, StreamErrorFor};
use combine::{attempt, between, choice, many1, not_followed_by, optional, parser, Parser};

use super::syntax::*;
Expand All @@ -13,10 +13,9 @@ where
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
let l_bool = choice((
str_("true").map(|_| Lit::LBool(true)),
(str_("false").map(|_| Lit::LBool(false))),
))
.skip(not_followed_by(letter()));
res_str("true").map(|_| Lit::LBool(true)),
(res_str("false").map(|_| Lit::LBool(false))),
));
let l_int = (optional(char('-')), integer()).map(|t| {
// TODO handle this error, even though it should be impossible
let string: String = t.1;
Expand All @@ -28,19 +27,19 @@ where
});
let lit = choice((l_bool, l_int)).map(|v| Expr::Lit(v));

let p_add = str_("+").map(|_| PrimOp::Add);
let p_sub = str_("-").map(|_| PrimOp::Sub);
let p_mul = str_("*").map(|_| PrimOp::Mul);
let p_eql = str_("==").map(|_| PrimOp::Eql);
let p_add = res_str("+").map(|_| PrimOp::Add);
let p_sub = res_str("-").map(|_| PrimOp::Sub);
let p_mul = res_str("*").map(|_| PrimOp::Mul);
let p_eql = res_str("==").map(|_| PrimOp::Eql);
let prim_op = choice((p_add, p_sub, p_mul, p_eql)).map(|v| Expr::Prim(v));

let app = (expr(), expr()).map(|t| Expr::App(Box::new(t.0), Box::new(t.1)));

let lam = (str_("lam "), lex_char('['), name(), lex_char(']'), expr())
let lam = (res_str("lam"), lex_char('['), name(), lex_char(']'), expr())
.map(|t| Expr::Lam(t.2, Box::new(t.4)));

let let_ = (
str_("let "),
res_str("let"),
lex_char('('),
lex_char('['),
name(),
Expand All @@ -51,10 +50,10 @@ where
)
.map(|t| Expr::Let(t.3, Box::new(t.4), Box::new(t.7)));

let if_ = (str_("if "), expr(), expr(), expr())
let if_ = (res_str("if"), expr(), expr(), expr())
.map(|t| Expr::If(Box::new(t.1), Box::new(t.2), Box::new(t.3)));

let fix = (str_("fix "), expr()).map(|t| Expr::Fix(Box::new(t.1)));
let fix = (res_str("fix"), expr()).map(|t| Expr::Fix(Box::new(t.1)));

let parenthesized = choice((attempt(lam), attempt(let_), attempt(if_), attempt(fix), app));

Expand Down Expand Up @@ -88,7 +87,7 @@ where
// Necessary due to rust-lang/rust#24159
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
let defn_ = (str_("defn "), name(), expr()).map(|t| Defn(t.1, t.2));
let defn_ = (res_str("defn"), name(), expr()).map(|t| Defn(t.1, t.2));

between(lex_char('('), lex_char(')'), defn_).skip(skip_spaces())
}
Expand Down Expand Up @@ -141,20 +140,37 @@ where
many1(digit()).skip(skip_spaces())
}

fn str_<'a, Input>(x: &'static str) -> impl Parser<Input, Output = &'a str>
fn res_str<'a, Input>(x: &'static str) -> impl Parser<Input, Output = &'a str>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
string(x).skip(skip_spaces())
string(x)
.skip(not_followed_by(alpha_num()))
.skip(skip_spaces())
}

pub fn reserved() -> Vec<String> {
["let", "lam", "fix", "true", "false", "if"]
.iter()
.map(|x| x.to_string())
.collect()
}

fn name<Input>() -> impl Parser<Input, Output = Name>
where
Input: Stream<Token = char>,
Input::Error: ParseError<Input::Token, Input::Range, Input::Position>,
{
word().map(Name)
word().and_then(move |s: String| {
if reserved().contains(&s) {
Err(StreamErrorFor::<Input>::unexpected_static_message(
"reserved keyword",
))
} else {
Ok(Name(s))
}
})
}

fn var<Input>() -> impl Parser<Input, Output = Expr>
Expand Down
2 changes: 1 addition & 1 deletion src/pretty.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use pretty::RcDoc;

use super::syntax::{Defn, Expr, Expr::*, Lit, Lit::*, Name, PrimOp, PrimOp::*};
use crate::util::pretty::parens;
use crate::sp;
use crate::util::pretty::parens;

impl Expr {
pub fn ppr(&self) -> RcDoc<()> {
Expand Down
14 changes: 10 additions & 4 deletions src/test/syntax.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use quickcheck::{empty_shrinker, single_shrinker, Arbitrary, Gen};
use rand::Rng;

use crate::parse::reserved;
use crate::syntax::*;

impl Arbitrary for Expr {
Expand Down Expand Up @@ -113,11 +114,16 @@ impl Arbitrary for Lit {
impl Arbitrary for Name {
fn arbitrary<G: Gen>(g: &mut G) -> Name {
let len = g.gen_range(3, 8);
let mut s = String::new();
for _ in 0..len {
s.push(gen_alpha_char(g));
let res = reserved();
loop {
let mut s = String::new();
for _ in 0..len {
s.push(gen_alpha_char(g));
}
if !res.contains(&s) {
return Name(s);
}
}
Name(s)
}
}

Expand Down
8 changes: 5 additions & 3 deletions src/types.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use pretty::RcDoc;

use crate::util::pretty::parens;
use crate::sp;
use crate::util::pretty::parens;

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Hash)]
pub struct TV(pub String);
Expand Down Expand Up @@ -34,7 +34,9 @@ impl Scheme {
RcDoc::text("")
} else {
let vars: Vec<RcDoc<()>> = tvs.iter().map(|tv| tv.ppr()).collect();
RcDoc::text("forall ").append(RcDoc::intersperse(vars, sp!())).append(RcDoc::text(". "))
RcDoc::text("forall ")
.append(RcDoc::intersperse(vars, sp!()))
.append(RcDoc::text(". "))
};

quantifier.append(ty.ppr())
Expand All @@ -46,7 +48,7 @@ impl Scheme {
impl TV {
pub fn ppr(&self) -> RcDoc<()> {
match self {
TV(s) => RcDoc::text(s)
TV(s) => RcDoc::text(s),
}
}
}
Expand Down

0 comments on commit e78f512

Please sign in to comment.