Merge pull request #263 from fitzgen/support-associated-types

Allow actions to return a grammar's type parameter's associated type
This commit is contained in:
Niko Matsakis 2017-09-21 09:25:36 -04:00 committed by GitHub
commit 08bb4c98b9
10 changed files with 1188 additions and 22 deletions

View File

@ -0,0 +1,11 @@
use std::str::FromStr;
use associated_types_lib::ParseCallbacks;
grammar<P>(callbacks: &mut P) where P: ParseCallbacks;
pub Term: P::Term = {
<n:Num> => n.into(),
"(" <t:Term> ")" => t,
};
Num: P::Num = <s:r"[0-9]+"> => callbacks.number(i32::from_str(s).unwrap());

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,32 @@
use std::fmt::Debug;
pub trait ParseCallbacks: Eq + Debug {
type Num: Eq + Debug + Into<Self::Term>;
type Term: Eq + Debug;
fn number(&mut self, i32) -> Self::Num;
}
#[derive(Eq, PartialEq, Debug)]
pub struct TestParseCallbacks;
#[derive(Eq, PartialEq, Debug)]
pub struct TestNum(pub i32);
#[derive(Eq, PartialEq, Debug)]
pub struct TestTerm(pub TestNum);
impl From<TestNum> for TestTerm {
fn from(n: TestNum) -> Self {
TestTerm(n)
}
}
impl ParseCallbacks for TestParseCallbacks {
type Num = TestNum;
type Term = TestTerm;
fn number(&mut self, n: i32) -> Self::Num {
TestNum(n)
}
}

View File

@ -7,6 +7,11 @@ use lalrpop_util::{ErrorRecovery, ParseError};
use util::tok::Tok;
/// Tests that actions can return the grammar's type parameters' associated
/// types.
mod associated_types;
mod associated_types_lib;
/// demonstration from the Greene text; one of the simplest grammars
/// that still ensures we get parse tree correct
mod sub;
@ -458,3 +463,12 @@ fn issue_113() {
fn issue_253() {
assert!(partial_parse::parse_Term("(22))").is_err());
}
#[test]
fn test_action_return_associated_types() {
let mut callbacks = associated_types_lib::TestParseCallbacks;
assert_eq!(
associated_types::parse_Term(&mut callbacks, "(((((42)))))"),
Ok(associated_types_lib::TestTerm(associated_types_lib::TestNum(42)))
);
}

View File

@ -61,7 +61,8 @@ mod __parse__Term {
}
#[allow(dead_code)]
pub enum __Nonterminal<> {
pub enum __Nonterminal<>
{
Num((usize, i32, usize)),
Term((usize, i32, usize)),
____Term((usize, i32, usize)),
@ -496,7 +497,8 @@ mod __parse__Term {
use std::str::FromStr;
extern crate lalrpop_util as __lalrpop_util;
#[allow(dead_code)]
pub enum __Symbol<'input> {
pub enum __Symbol<'input>
{
Term_22_28_22(&'input str),
Term_22_29_22(&'input str),
Termr_23_22_5b0_2d9_5d_2b_22_23(&'input str),
@ -778,7 +780,8 @@ mod __parse__Term {
'input,
>(
__symbols: &mut ::std::vec::Vec<(usize,__Symbol<'input>,usize)>
) -> (usize, &'input str, usize) {
) -> (usize, &'input str, usize)
{
match __symbols.pop().unwrap() {
(__l, __Symbol::Term_22_28_22(__v), __r) => (__l, __v, __r),
_ => panic!("symbol type mismatch")
@ -788,7 +791,8 @@ mod __parse__Term {
'input,
>(
__symbols: &mut ::std::vec::Vec<(usize,__Symbol<'input>,usize)>
) -> (usize, &'input str, usize) {
) -> (usize, &'input str, usize)
{
match __symbols.pop().unwrap() {
(__l, __Symbol::Term_22_29_22(__v), __r) => (__l, __v, __r),
_ => panic!("symbol type mismatch")
@ -798,7 +802,8 @@ mod __parse__Term {
'input,
>(
__symbols: &mut ::std::vec::Vec<(usize,__Symbol<'input>,usize)>
) -> (usize, &'input str, usize) {
) -> (usize, &'input str, usize)
{
match __symbols.pop().unwrap() {
(__l, __Symbol::Termr_23_22_5b0_2d9_5d_2b_22_23(__v), __r) => (__l, __v, __r),
_ => panic!("symbol type mismatch")
@ -808,7 +813,8 @@ mod __parse__Term {
'input,
>(
__symbols: &mut ::std::vec::Vec<(usize,__Symbol<'input>,usize)>
) -> (usize, i32, usize) {
) -> (usize, i32, usize)
{
match __symbols.pop().unwrap() {
(__l, __Symbol::NtNum(__v), __r) => (__l, __v, __r),
_ => panic!("symbol type mismatch")
@ -818,7 +824,8 @@ mod __parse__Term {
'input,
>(
__symbols: &mut ::std::vec::Vec<(usize,__Symbol<'input>,usize)>
) -> (usize, i32, usize) {
) -> (usize, i32, usize)
{
match __symbols.pop().unwrap() {
(__l, __Symbol::NtTerm(__v), __r) => (__l, __v, __r),
_ => panic!("symbol type mismatch")
@ -828,7 +835,8 @@ mod __parse__Term {
'input,
>(
__symbols: &mut ::std::vec::Vec<(usize,__Symbol<'input>,usize)>
) -> (usize, i32, usize) {
) -> (usize, i32, usize)
{
match __symbols.pop().unwrap() {
(__l, __Symbol::Nt____Term(__v), __r) => (__l, __v, __r),
_ => panic!("symbol type mismatch")

View File

@ -189,7 +189,7 @@ pub struct Conversion {
pub to: Pattern<TypeRef>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Path {
pub absolute: bool,
pub ids: Vec<InternedString>,
@ -222,7 +222,7 @@ pub enum TypeRef {
OfSymbol(SymbolKind),
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum WhereClause<T> {
// 'a: 'b + 'c
Lifetime {
@ -255,7 +255,7 @@ impl<T> WhereClause<T> {
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum TypeBound<T> {
// The `'a` in `T: 'a`.
Lifetime(InternedString),
@ -296,7 +296,7 @@ impl<T> TypeBound<T> {
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum TypeBoundParameter<T> {
// 'a
Lifetime(InternedString),

View File

@ -167,10 +167,14 @@ pub enum InlinedSymbol {
Inlined(ActionFn, Vec<Symbol>),
}
#[derive(Clone, PartialEq, Eq)]
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum TypeRepr {
Tuple(Vec<TypeRepr>),
Nominal(NominalTypeRepr),
Associated {
type_parameter: InternedString,
id: InternedString,
},
Lifetime(InternedString),
Ref {
lifetime: Option<InternedString>,
@ -218,6 +222,8 @@ impl TypeRepr {
None => vec![],
})
.collect(),
TypeRepr::Associated { type_parameter, .. } =>
vec![TypeParameter::Id(type_parameter)],
TypeRepr::Lifetime(l) =>
vec![TypeParameter::Lifetime(l)],
TypeRepr::Ref { ref lifetime, mutable: _, ref referent } =>
@ -229,7 +235,7 @@ impl TypeRepr {
}
}
#[derive(Clone, PartialEq, Eq)]
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct NominalTypeRepr {
pub path: Path,
pub types: Vec<TypeRepr>
@ -368,6 +374,8 @@ impl Display for TypeRepr {
write!(fmt, "({})", Sep(", ", types)),
TypeRepr::Nominal(ref data) =>
write!(fmt, "{}", data),
TypeRepr::Associated { type_parameter, id } =>
write!(fmt, "{}::{}", type_parameter, id),
TypeRepr::Lifetime(id) =>
write!(fmt, "{}", id),
TypeRepr::Ref { lifetime: None, mutable: false, ref referent } =>

View File

@ -3,6 +3,7 @@
//! [recursive ascent]: https://en.wikipedia.org/wiki/Recursive_ascent_parser
use collections::{Multimap, Set};
use grammar::parse_tree::WhereClause;
use grammar::repr::{Grammar, NonterminalString, Production, Symbol, TerminalString, TypeParameter,
TypeRepr};
use lr1::core::*;
@ -42,6 +43,8 @@ struct RecursiveAscent<'ascent, 'grammar> {
/// type parameters for the `Nonterminal` type
nonterminal_type_params: Vec<TypeParameter>,
nonterminal_where_clauses: Vec<WhereClause<TypeRepr>>
}
/// Tracks the suffix of the stack (that is, top-most elements) that any
@ -134,6 +137,21 @@ impl<'ascent, 'grammar, W: Write> CodeGenerator<'ascent,
.cloned()
.collect();
let mut referenced_where_clauses = Set::new();
for wc in &grammar.where_clauses {
wc.map(|ty| {
if ty.referenced().iter().any(|p| nonterminal_type_params.contains(p)) {
referenced_where_clauses.insert(wc.clone());
}
});
}
let nonterminal_where_clauses: Vec<_> = grammar.where_clauses
.iter()
.filter(|wc| referenced_where_clauses.contains(wc))
.cloned()
.collect();
let state_inputs = states.iter()
.map(|state| Self::state_input_for(state))
.collect();
@ -149,6 +167,7 @@ impl<'ascent, 'grammar, W: Write> CodeGenerator<'ascent,
graph: graph,
state_inputs: state_inputs,
nonterminal_type_params: nonterminal_type_params,
nonterminal_where_clauses: nonterminal_where_clauses,
})
}
@ -179,10 +198,16 @@ impl<'ascent, 'grammar, W: Write> CodeGenerator<'ascent,
// if we are generating multiple parsers from the same file:
rust!(self.out, "#[allow(dead_code)]");
rust!(self.out,
"pub enum {}Nonterminal<{}> {{",
"pub enum {}Nonterminal<{}>",
self.prefix,
Sep(", ", &self.custom.nonterminal_type_params));
if !self.custom.nonterminal_where_clauses.is_empty() {
rust!(self.out, " where {}", Sep(", ", &self.custom.nonterminal_where_clauses));
}
rust!(self.out, " {{");
// make an enum with one variant per nonterminal; I considered
// making different enums per state, but this would mean we
// have to unwrap and rewrap as we pass up the stack, which

View File

@ -1,6 +1,7 @@
//! A compiler from an LR(1) table to a traditional table driven parser.
use collections::{Map, Set};
use grammar::parse_tree::WhereClause;
use grammar::repr::*;
use lr1::core::*;
use lr1::lookahead::Token;
@ -221,7 +222,7 @@ enum Comment<'a, T> {
impl<'a, T: fmt::Display> fmt::Display for Comment<'a, T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Comment::Goto(ref token, new_state) =>
Comment::Goto(ref token, new_state) =>
write!(f, " // on {}, goto {}", token, new_state),
Comment::Error(ref token) =>
write!(f, " // on {}, error", token),
@ -235,6 +236,8 @@ struct TableDriven<'grammar> {
/// type parameters for the `Nonterminal` type
symbol_type_params: Vec<TypeParameter>,
symbol_where_clauses: Vec<WhereClause<TypeRepr>>,
/// a list of each nonterminal in some specific order
all_nonterminals: Vec<NonterminalString>,
@ -268,6 +271,21 @@ impl<'ascent, 'grammar, W: Write> CodeGenerator<'ascent, 'grammar, W, TableDrive
.cloned()
.collect();
let mut referenced_where_clauses = Set::new();
for wc in &grammar.where_clauses {
wc.map(|ty| {
if ty.referenced().iter().any(|p| symbol_type_params.contains(p)) {
referenced_where_clauses.insert(wc.clone());
}
});
}
let symbol_where_clauses: Vec<_> = grammar.where_clauses
.iter()
.filter(|wc| referenced_where_clauses.contains(wc))
.cloned()
.collect();
// Assign each production a unique index to use as the values for reduce
// actions in the ACTION and EOF_ACTION tables.
let reduce_indices: Map<&'grammar Production, usize> = grammar.nonterminals
@ -287,6 +305,7 @@ impl<'ascent, 'grammar, W: Write> CodeGenerator<'ascent, 'grammar, W, TableDrive
action_module,
TableDriven {
symbol_type_params: symbol_type_params,
symbol_where_clauses: symbol_where_clauses,
all_nonterminals: grammar.nonterminals
.keys()
.cloned()
@ -311,10 +330,16 @@ impl<'ascent, 'grammar, W: Write> CodeGenerator<'ascent, 'grammar, W, TableDrive
// if we are generating multiple parsers from the same file:
rust!(self.out, "#[allow(dead_code)]");
rust!(self.out,
"pub enum {}Symbol<{}> {{",
"pub enum {}Symbol<{}>",
self.prefix,
Sep(", ", &self.custom.symbol_type_params));
if !self.custom.symbol_where_clauses.is_empty() {
rust!(self.out, " where {}", Sep(", ", &self.custom.symbol_where_clauses));
}
rust!(self.out, " {{");
// make one variant per terminal
for &term in &self.grammar.terminals.all {
let name = self.variant_name_for_symbol(Symbol::Terminal(term));
@ -661,7 +686,7 @@ impl<'ascent, 'grammar, W: Write> CodeGenerator<'ascent, 'grammar, W, TableDrive
}
rust!(self.out, "}}"); // else
rust!(self.out, "}}"); // while let
self.end_parser_fn()
@ -987,7 +1012,13 @@ impl<'ascent, 'grammar, W: Write> CodeGenerator<'ascent, 'grammar, W, TableDrive
"{}symbols: &mut ::std::vec::Vec<{}>",
self.prefix,
spanned_symbol_type);
rust!(self.out, ") -> {} {{", self.types.spanned_type(variant_ty));
rust!(self.out, ") -> {}", self.types.spanned_type(variant_ty));
if !self.custom.symbol_where_clauses.is_empty() {
rust!(self.out, " where {}", Sep(", ", &self.custom.symbol_where_clauses));
}
rust!(self.out, " {{");
if DEBUG_PRINT {
rust!(self.out, "println!(\"pop_{}\");", variant_name);

View File

@ -1,7 +1,7 @@
use super::{NormResult, NormError};
use super::norm_util::{self, AlternativeAction, Symbols};
use std::collections::{HashMap};
use std::collections::{HashMap, HashSet};
use grammar::consts::{ERROR, INPUT_LIFETIME, LOCATION};
use grammar::parse_tree::{ActionKind, Alternative,
Grammar,
@ -9,9 +9,10 @@ use grammar::parse_tree::{ActionKind, Alternative,
Path,
Span,
SymbolKind,
TypeParameter,
TypeRef};
use grammar::repr::{NominalTypeRepr, Types, TypeRepr};
use intern::intern;
use intern::{intern, InternedString};
#[cfg(test)]
mod test;
@ -25,6 +26,7 @@ struct TypeInferencer<'grammar> {
stack: Vec<NonterminalString>,
nonterminals: HashMap<NonterminalString, NT<'grammar>>,
types: Types,
type_parameters: HashSet<InternedString>,
}
#[derive(Copy, Clone)]
@ -48,9 +50,18 @@ impl<'grammar> TypeInferencer<'grammar> {
})
.collect();
let type_parameters = grammar.type_parameters
.iter()
.filter_map(|p| match *p {
TypeParameter::Lifetime(_) => None,
TypeParameter::Id(ty) => Some(ty),
})
.collect();
Ok(TypeInferencer { stack: vec![],
nonterminals: nonterminals,
types: types })
types: types,
type_parameters: type_parameters })
}
fn make_types(grammar: &Grammar) -> Types {
@ -212,6 +223,13 @@ impl<'grammar> TypeInferencer<'grammar> {
Ok(TypeRepr::Tuple(types))
}
TypeRef::Nominal { ref path, ref types } => {
if path.ids.len() == 2 && self.type_parameters.contains(&path.ids[0]) {
return Ok(TypeRepr::Associated {
type_parameter: path.ids[0],
id: path.ids[1],
});
}
let types = try! {
types.iter().map(|t| self.type_ref(t)).collect()
};