third_party/rust/lalrpop/src/build/action.rs
author Dorel Luca <dluca@mozilla.com>
Sat, 12 Jan 2019 03:43:46 +0200
changeset 453620 def9811f0311
parent 453619 3c4b8e03e722
child 460816 c5e856d5edba
permissions -rw-r--r--
Backed out 2 changesets (bug 1516337) for build bustage. CLOSED TREE Backed out changeset 3c4b8e03e722 (bug 1516337) Backed out changeset 4fc377013db5 (bug 1516337)

//! Code for generating action code.
//!
//! From the outside, action fns have one of two forms. If they take
//! symbols as input, e.g. from a production like `X = Y Z => ...`
//! (which takes Y and Z as input), they have this form:
//!
//! ```
//! fn __action17<
//!     'input,                       // user-declared type parameters (*)
//! >(
//!     input: &'input str,           // user-declared parameters
//!     __0: (usize, usize, usize),   // symbols being reduced, if any
//!     ...
//!     __N: (usize, Foo, usize),     // each has a type (L, T, L)
//! ) -> Box<Expr<'input>>
//! ```
//!
//! Otherwise, they have this form:
//!
//! ```
//! fn __action17<
//!     'input,                       // user-declared type parameters (*)
//! >(
//!     input: &'input str,           // user-declared parameters
//!    __lookbehind: &usize,          // value for @R -- "end of previous token"
//!    __lookahead: &usize,           // value for @L -- "start of next token"
//! ) -> Box<Expr<'input>>
//! ```
//!
//! * -- in this case, those "user-declared" parameters are inserted by
//! the "internal tokenizer".

use grammar::repr as r;
use rust::RustWrite;
use std::io::{self, Write};

pub fn emit_action_code<W: Write>(grammar: &r::Grammar, rust: &mut RustWrite<W>) -> io::Result<()> {
    for (i, defn) in grammar.action_fn_defns.iter().enumerate() {
        rust!(rust, "");

        // we always thread the parameters through to the action code,
        // even if they are not used, and hence we need to disable the
        // unused variables lint, which otherwise gets very excited.
        if !grammar.parameters.is_empty() {
            rust!(rust, "#[allow(unused_variables)]");
        }

        match defn.kind {
            r::ActionFnDefnKind::User(ref data) => {
                try!(emit_user_action_code(grammar, rust, i, defn, data))
            }
            r::ActionFnDefnKind::Lookaround(ref variant) => {
                try!(emit_lookaround_action_code(grammar, rust, i, defn, variant))
            }
            r::ActionFnDefnKind::Inline(ref data) => {
                try!(emit_inline_action_code(grammar, rust, i, defn, data))
            }
        }
    }

    Ok(())
}

fn ret_type_string(grammar: &r::Grammar, defn: &r::ActionFnDefn) -> String {
    if defn.fallible {
        format!(
            "Result<{},{}lalrpop_util::ParseError<{},{},{}>>",
            defn.ret_type,
            grammar.prefix,
            grammar.types.terminal_loc_type(),
            grammar.types.terminal_token_type(),
            grammar.types.error_type()
        )
    } else {
        format!("{}", defn.ret_type)
    }
}

fn emit_user_action_code<W: Write>(
    grammar: &r::Grammar,
    rust: &mut RustWrite<W>,
    index: usize,
    defn: &r::ActionFnDefn,
    data: &r::UserActionFnDefn,
) -> io::Result<()> {
    let ret_type = ret_type_string(grammar, defn);

    // For each symbol to be reduced, we will receive
    // a (L, T, L) triple where the Ls are locations and
    // the T is the data. Ignore the locations and bind
    // the data to the name the user gave.
    let mut arguments: Vec<String> = data.arg_patterns
        .iter()
        .zip(
            data.arg_types
                .iter()
                .cloned()
                .map(|t| grammar.types.spanned_type(t)),
        )
        .map(|(p, t)| format!("(_, {}, _): {}", p, t))
        .collect();

    // If this is a reduce of an empty production, we will
    // automatically add position information in the form of
    // lookbehind/lookahead values. Otherwise, those values would be
    // determined from the arguments themselves.
    if data.arg_patterns.is_empty() {
        arguments.extend(vec![
            format!(
                "{}lookbehind: &{}",
                grammar.prefix,
                grammar.types.terminal_loc_type()
            ),
            format!(
                "{}lookahead: &{}",
                grammar.prefix,
                grammar.types.terminal_loc_type()
            ),
        ]);
    }

    try!(rust.write_fn_header(
        grammar,
        &r::Visibility::Priv,
        format!("{}action{}", grammar.prefix, index),
        vec![],
        None,
        arguments,
        ret_type,
        vec![]
    ));
    rust!(rust, "{{");
    rust!(rust, "{}", data.code);
    rust!(rust, "}}");
    Ok(())
}

fn emit_lookaround_action_code<W: Write>(
    grammar: &r::Grammar,
    rust: &mut RustWrite<W>,
    index: usize,
    _defn: &r::ActionFnDefn,
    data: &r::LookaroundActionFnDefn,
) -> io::Result<()> {
    try!(rust.write_fn_header(
        grammar,
        &r::Visibility::Priv,
        format!("{}action{}", grammar.prefix, index),
        vec![],
        None,
        vec![
            format!(
                "{}lookbehind: &{}",
                grammar.prefix,
                grammar.types.terminal_loc_type()
            ),
            format!(
                "{}lookahead: &{}",
                grammar.prefix,
                grammar.types.terminal_loc_type()
            ),
        ],
        format!("{}", grammar.types.terminal_loc_type()),
        vec![]
    ));

    rust!(rust, "{{");
    match *data {
        r::LookaroundActionFnDefn::Lookahead => {
            // take the lookahead, if any; otherwise, we are
            // at EOF, so taker the lookbehind (end of last
            // pushed token); if that is missing too, then
            // supply default.
            rust!(rust, "{}lookahead.clone()", grammar.prefix);
        }
        r::LookaroundActionFnDefn::Lookbehind => {
            // take lookbehind or supply default
            rust!(rust, "{}lookbehind.clone()", grammar.prefix);
        }
    }
    rust!(rust, "}}");
    Ok(())
}

fn emit_inline_action_code<W: Write>(
    grammar: &r::Grammar,
    rust: &mut RustWrite<W>,
    index: usize,
    defn: &r::ActionFnDefn,
    data: &r::InlineActionFnDefn,
) -> io::Result<()> {
    let ret_type = ret_type_string(grammar, defn);

    let arg_types: Vec<_> = data.symbols
        .iter()
        .flat_map(|sym| match *sym {
            r::InlinedSymbol::Original(ref s) => vec![s.clone()],
            r::InlinedSymbol::Inlined(_, ref syms) => syms.clone(),
        })
        .map(|s| s.ty(&grammar.types))
        .collect();

    // this is the number of symbols we expect to be passed in; it is
    // distinct from data.symbols.len(), because sometimes we have
    // inlined actions with no input symbols
    let num_flat_args = arg_types.len();

    let mut arguments: Vec<_> = arg_types
        .iter()
        .map(|&t| grammar.types.spanned_type(t.clone()))
        .enumerate()
        .map(|(i, t)| format!("{}{}: {}", grammar.prefix, i, t))
        .collect();

    // If no symbols are being reduced, add in the
    // lookbehind/lookahead.
    if arguments.len() == 0 {
        arguments.extend(vec![
            format!(
                "{}lookbehind: &{}",
                grammar.prefix,
                grammar.types.terminal_loc_type()
            ),
            format!(
                "{}lookahead: &{}",
                grammar.prefix,
                grammar.types.terminal_loc_type()
            ),
        ]);
    }

    try!(rust.write_fn_header(
        grammar,
        &r::Visibility::Priv,
        format!("{}action{}", grammar.prefix, index),
        vec![],
        None,
        arguments,
        ret_type,
        vec![]
    ));
    rust!(rust, "{{");

    // For each inlined thing, compute the start/end locations.
    // Do this first so that none of the arguments have been moved
    // yet and we can easily access their locations.
    let mut arg_counter = 0;
    let mut temp_counter = 0;
    for symbol in &data.symbols {
        match *symbol {
            r::InlinedSymbol::Original(_) => {
                arg_counter += 1;
            }
            r::InlinedSymbol::Inlined(_, ref syms) => {
                if syms.len() > 0 {
                    // If we are reducing symbols, then start and end
                    // can be the start/end location of the first/last
                    // symbol respectively. Easy peezy.

                    rust!(
                        rust,
                        "let {}start{} = {}{}.0.clone();",
                        grammar.prefix,
                        temp_counter,
                        grammar.prefix,
                        arg_counter
                    );

                    let last_arg_index = arg_counter + syms.len() - 1;
                    rust!(
                        rust,
                        "let {}end{} = {}{}.2.clone();",
                        grammar.prefix,
                        temp_counter,
                        grammar.prefix,
                        last_arg_index
                    );
                } else {
                    // If we have no symbols, then `arg_counter`
                    // represents index of the first symbol after this
                    // inlined item (if any), and `arg_counter-1`
                    // represents index of the symbol before this
                    // item.

                    if arg_counter > 0 {
                        rust!(
                            rust,
                            "let {}start{} = {}{}.2.clone();",
                            grammar.prefix,
                            temp_counter,
                            grammar.prefix,
                            arg_counter - 1
                        );
                    } else if num_flat_args > 0 {
                        rust!(
                            rust,
                            "let {}start{} = {}{}.0.clone();",
                            grammar.prefix,
                            temp_counter,
                            grammar.prefix,
                            arg_counter
                        );
                    } else {
                        rust!(
                            rust,
                            "let {}start{} = {}lookbehind.clone();",
                            grammar.prefix,
                            temp_counter,
                            grammar.prefix
                        );
                    }

                    if arg_counter < num_flat_args {
                        rust!(
                            rust,
                            "let {}end{} = {}{}.0.clone();",
                            grammar.prefix,
                            temp_counter,
                            grammar.prefix,
                            arg_counter
                        );
                    } else if num_flat_args > 0 {
                        rust!(
                            rust,
                            "let {}end{} = {}{}.2.clone();",
                            grammar.prefix,
                            temp_counter,
                            grammar.prefix,
                            num_flat_args - 1
                        );
                    } else {
                        rust!(
                            rust,
                            "let {}end{} = {}lookahead.clone();",
                            grammar.prefix,
                            temp_counter,
                            grammar.prefix
                        );
                    }
                }

                temp_counter += 1;
                arg_counter += syms.len();
            }
        }
    }

    // Now create temporaries for the inlined things
    let mut arg_counter = 0;
    let mut temp_counter = 0;
    for symbol in &data.symbols {
        match *symbol {
            r::InlinedSymbol::Original(_) => {
                arg_counter += 1;
            }
            r::InlinedSymbol::Inlined(inlined_action, ref syms) => {
                // execute the inlined reduce action
                rust!(
                    rust,
                    "let {}temp{} = {}action{}(",
                    grammar.prefix,
                    temp_counter,
                    grammar.prefix,
                    inlined_action.index()
                );
                for parameter in &grammar.parameters {
                    rust!(rust, "{},", parameter.name);
                }
                for i in 0..syms.len() {
                    rust!(rust, "{}{},", grammar.prefix, arg_counter + i);
                }
                if syms.len() == 0 {
                    rust!(rust, "&{}start{},", grammar.prefix, temp_counter);
                    rust!(rust, "&{}end{},", grammar.prefix, temp_counter);
                }
                rust!(rust, ");");

                // wrap up the inlined value along with its span
                rust!(
                    rust,
                    "let {}temp{} = ({}start{}, {}temp{}, {}end{});",
                    grammar.prefix,
                    temp_counter,
                    grammar.prefix,
                    temp_counter,
                    grammar.prefix,
                    temp_counter,
                    grammar.prefix,
                    temp_counter
                );

                temp_counter += 1;
                arg_counter += syms.len();
            }
        }
    }

    rust!(rust, "{}action{}(", grammar.prefix, data.action.index());
    for parameter in &grammar.parameters {
        rust!(rust, "{},", parameter.name);
    }
    let mut arg_counter = 0;
    let mut temp_counter = 0;
    for symbol in &data.symbols {
        match *symbol {
            r::InlinedSymbol::Original(_) => {
                rust!(rust, "{}{},", grammar.prefix, arg_counter);
                arg_counter += 1;
            }
            r::InlinedSymbol::Inlined(_, ref syms) => {
                rust!(rust, "{}temp{},", grammar.prefix, temp_counter);
                temp_counter += 1;
                arg_counter += syms.len();
            }
        }
    }
    assert!(data.symbols.len() > 0);
    rust!(rust, ")");

    rust!(rust, "}}");
    Ok(())
}