ldscript-parser 0.3.0

A Linker Script file parser
Documentation
use commands::{command, Command};
use expressions::expression;
use expressions::Expression;
use idents::pattern;
use idents::symbol;
use nom::branch::alt;
use nom::bytes::complete::tag;
use nom::combinator::cut;
use nom::combinator::map;
use nom::combinator::opt;
use nom::multi::many0;
use nom::multi::many1;
use nom::sequence::delimited;
use nom::sequence::preceded;
use nom::sequence::tuple;
use nom::IResult;
use statements::{statement, Statement};
use whitespace::opt_space;

#[derive(Debug, PartialEq)]
pub enum SectionCommand {
    Statement(Statement),
    Command(Command),
    OutputSection {
        name: String,
        vma_address: Option<Box<Expression>>,
        s_type: Option<OutputSectionType>,
        lma_address: Option<Box<Expression>>,
        section_align: Option<Box<Expression>>,
        align_with_input: bool,
        subsection_align: Option<Box<Expression>>,
        constraint: Option<OutputSectionConstraint>,
        content: Vec<OutputSectionCommand>,
        region: Option<String>,
        lma_region: Option<String>,
        fillexp: Option<Box<Expression>>,
    },
}

#[derive(Debug, PartialEq)]
pub enum OutputSectionCommand {
    Statement(Statement),
    Fill {
        expr: Box<Expression>,
    },
    Data {
        d_type: DataType,
        value: Box<Expression>,
    },
    InputSection {
        file: SectionPattern,
        sections: Vec<SectionPattern>,
    },
    KeepInputSection {
        file: SectionPattern,
        sections: Vec<SectionPattern>,
    },
}

#[derive(Debug, PartialEq)]
pub enum DataType {
    Byte,
    Short,
    Long,
    Quad,
}

#[derive(Debug, PartialEq)]
pub enum SectionPattern {
    Simple(String),
    SortByName(String),
    SortByAlignment(String),
    SortByInitPriority(String),
    SortNone(String),
    ExcludeFile {
        files: Vec<String>,
        pattern: Box<SectionPattern>,
    },
}

#[derive(Debug, PartialEq)]
pub enum OutputSectionType {
    NoLoad,
    DSect,
    Copy,
    Info,
    Overlay,
}

#[derive(Debug, PartialEq)]
pub enum OutputSectionConstraint {
    OnlyIfRo,
    OnlyIfRw,
}

fn output_section_type(input: &str) -> IResult<&str, OutputSectionType> {
    alt((
        map(tag("(NOLOAD)"), |_| OutputSectionType::NoLoad),
        map(tag("(DSECT)"), |_| OutputSectionType::DSect),
        map(tag("(COPY)"), |_| OutputSectionType::Copy),
        map(tag("(INFO)"), |_| OutputSectionType::Info),
        map(tag("(OVERLAY)"), |_| OutputSectionType::Overlay),
    ))(input)
}

fn output_section_constraint(input: &str) -> IResult<&str, OutputSectionConstraint> {
    alt((
        map(tag("ONLY_IF_RO"), |_| OutputSectionConstraint::OnlyIfRo),
        map(tag("ONLY_IF_RW"), |_| OutputSectionConstraint::OnlyIfRw),
    ))(input)
}

fn sorted_sp(input: &str) -> IResult<&str, SectionPattern> {
    let (input, keyword) = alt((
        tag("SORT_BY_NAME"),
        tag("SORT_BY_ALIGNMENT"),
        tag("SORT_BY_INIT_PRIORITY"),
        tag("SORT_NONE"),
        tag("SORT"),
    ))(input)?;
    let (input, _) = cut(wsc!(tag("(")))(input)?;
    let (input, inner) = cut(pattern)(input)?;
    let (input, _) = cut(opt_space)(input)?;
    let (input, _) = cut(tag(")"))(input)?;
    Ok((
        input,
        match keyword {
            "SORT" | "SORT_BY_NAME" => SectionPattern::SortByName(inner.into()),
            "SORT_BY_ALIGNMENT" => SectionPattern::SortByAlignment(inner.into()),
            "SORT_BY_INIT_PRIORITY" => SectionPattern::SortByInitPriority(inner.into()),
            "SORT_NONE" => SectionPattern::SortNone(inner.into()),
            _ => panic!("wrong sort keyword"),
        },
    ))
}

fn exclude_file_sp(input: &str) -> IResult<&str, SectionPattern> {
    let (input, _) = tuple((tag("EXCLUDE_FILE"), opt_space, tag("(")))(input)?;
    let (input, files) = cut(many1(wsc!(map(pattern, String::from))))(input)?;
    let (input, _) = cut(tuple((tag(")"), opt_space)))(input)?;
    let (input, inner) = cut(section_pattern)(input)?;
    Ok((
        input,
        SectionPattern::ExcludeFile {
            files: files,
            pattern: Box::new(inner),
        },
    ))
}

fn simple_sp(input: &str) -> IResult<&str, SectionPattern> {
    map(pattern, |x: &str| SectionPattern::Simple(x.into()))(input)
}

fn section_pattern(input: &str) -> IResult<&str, SectionPattern> {
    alt((exclude_file_sp, sorted_sp, simple_sp))(input)
}

fn data_osc(input: &str) -> IResult<&str, OutputSectionCommand> {
    let (input, d_type) = alt((tag("BYTE"), tag("SHORT"), tag("LONG"), tag("QUAD")))(input)?;
    let (input, _) = wsc!(tag("("))(input)?;
    let (input, value) = expression(input)?;
    let (input, _) = tuple((wsc!(tag(")")), opt(tag(";"))))(input)?;
    Ok((
        input,
        OutputSectionCommand::Data {
            d_type: match d_type {
                "BYTE" => DataType::Byte,
                "SHORT" => DataType::Short,
                "LONG" => DataType::Long,
                "QUAD" => DataType::Quad,
                _ => panic!("invalid data type"),
            },
            value: Box::new(value),
        },
    ))
}

fn fill_osc(input: &str) -> IResult<&str, OutputSectionCommand> {
    let (input, _) = tuple((tag("FILL"), wsc!(tag("("))))(input)?;
    let (input, expr) = expression(input)?;
    let (input, _) = tuple((wsc!(tag(")")), opt(tag(";"))))(input)?;
    Ok((
        input,
        OutputSectionCommand::Fill {
            expr: Box::new(expr),
        },
    ))
}

fn statement_osc(input: &str) -> IResult<&str, OutputSectionCommand> {
    map(statement, |stmt| OutputSectionCommand::Statement(stmt))(input)
}

fn input_osc(input: &str) -> IResult<&str, OutputSectionCommand> {
    let (input, file) = section_pattern(input)?;
    let (input, _) = opt_space(input)?;
    let (input, sections) = opt(delimited(
        wsc!(tag("(")),
        many1(wsc!(section_pattern)),
        wsc!(tag(")")),
    ))(input)?;
    Ok((
        input,
        OutputSectionCommand::InputSection {
            file: file,
            sections: match sections {
                Some(s) => s,
                None => Vec::new(),
            },
        },
    ))
}

fn keep_osc(input: &str) -> IResult<&str, OutputSectionCommand> {
    let (input, _) = tuple((tag("KEEP"), wsc!(tag("("))))(input)?;
    let (input, inner) = input_osc(input)?;
    let (input, _) = wsc!(tag(")"))(input)?;
    Ok((
        input,
        match inner {
            OutputSectionCommand::InputSection { file, sections } => {
                OutputSectionCommand::KeepInputSection {
                    file: file,
                    sections: sections,
                }
            }
            _ => panic!("wrong output section command"),
        },
    ))
}

fn output_section_command(input: &str) -> IResult<&str, OutputSectionCommand> {
    alt((statement_osc, keep_osc, data_osc, fill_osc, input_osc))(input)
}

fn statement_sc(input: &str) -> IResult<&str, SectionCommand> {
    map(statement, |stmt| SectionCommand::Statement(stmt))(input)
}

fn command_sc(input: &str) -> IResult<&str, SectionCommand> {
    map(command, |cmd| SectionCommand::Command(cmd))(input)
}

fn output_sc(input: &str) -> IResult<&str, SectionCommand> {
    let (input, name) = alt((tag("/DISCARD/"), symbol))(input)?;
    let (input, _) = opt_space(input)?;
    let (input, s_type1) = opt(output_section_type)(input)?;
    let (input, vma) = wsc!(opt(expression))(input)?;
    let (input, s_type2) = opt(output_section_type)(input)?;
    let (input, _) = wsc!(tag(":"))(input)?;
    let (input, lma) = opt(delimited(tag("AT("), wsc!(expression), tag(")")))(input)?;
    let (input, _) = opt_space(input)?;
    let (input, section_align) = opt(delimited(tag("ALIGN("), wsc!(expression), tag(")")))(input)?;
    let (input, align_with_input) = wsc!(opt(tag("ALIGN_WITH_INPUT")))(input)?;
    let (input, subsection_align) =
        opt(delimited(tag("SUBALIGN("), wsc!(expression), tag(")")))(input)?;
    let (input, constraint) = wsc!(opt(output_section_constraint))(input)?;
    let (input, _) = wsc!(tag("{"))(input)?;
    let (input, content) = many0(wsc!(output_section_command))(input)?;
    let (input, _) = wsc!(tag("}"))(input)?;
    let (input, region) = opt(preceded(tag(">"), wsc!(symbol)))(input)?;
    let (input, lma_region) = opt(preceded(tag("AT>"), wsc!(symbol)))(input)?;
    let (input, fillexp) = opt(preceded(tag("="), wsc!(expression)))(input)?;
    let (input, _) = opt(tag(","))(input)?;
    Ok((
        input,
        SectionCommand::OutputSection {
            name: name.into(),
            vma_address: vma.map(Box::new),
            s_type: if s_type1.is_some() { s_type1 } else { s_type2 },
            lma_address: lma.map(Box::new),
            section_align: section_align.map(Box::new),
            align_with_input: align_with_input.is_some(),
            subsection_align: subsection_align.map(Box::new),
            constraint: constraint,
            content: content,
            region: region.map(String::from),
            lma_region: lma_region.map(String::from),
            fillexp: fillexp.map(Box::new),
        },
    ))
}

pub fn section_command(input: &str) -> IResult<&str, SectionCommand> {
    alt((statement_sc, output_sc, command_sc))(input)
}

#[cfg(test)]
mod tests {
    use sections::*;

    #[test]
    fn test_section_command() {
        assert_fail!(section_pattern("EXCLUDE_FILE (*a)"));
        assert_fail!(input_osc("EXCLUDE_FILE (*a)"));
        assert_done!(section_pattern("EXCLUDE_FILE ( *a *b ) .c"));
        assert_done!(input_osc("EXCLUDE_FILE ( *a *b ) *c"));

        assert_fail!(input_osc("EXCLUDE_FILE ( EXCLUDE_FILE ( *a *b ) *c ) .d"));
        assert_done!(input_osc("EXCLUDE_FILE ( *a ) *b ( .c )"));
        assert_done!(input_osc("EXCLUDE_FILE ( *a ) *b ( .c .d )"));
        assert_done!(input_osc(
            "EXCLUDE_FILE ( *a ) *b ( .c EXCLUDE_FILE ( *a ) .d )",
        ));

        assert_done!(output_section_command("[A-Z]*(.data)"));
        assert_done!(output_section_command(
            "LONG((__CTOR_END__ - __CTOR_LIST__) / 4 - 2)",
        ));
        assert_done!(output_section_command(
            "EXCLUDE_FILE (*crtend.o *otherfile.o) *(.ctors)",
        ));
        assert_done!(output_section_command(
            "*(EXCLUDE_FILE (*crtend.o *otherfile.o) .ctors)",
        ));
        assert_done!(output_section_command(
            "*(EXCLUDE_FILE (*a) .text EXCLUDE_FILE (*b) .c)",
        ));
        assert_done!(output_section_command("KEEP(SORT_BY_NAME(*)(.ctors))"));
        assert_done!(output_section_command("PROVIDE (__init_array_end = .);"));
        assert_done!(output_section_command("LONG(0);"));
        assert_done!(output_section_command("SORT(CONSTRUCTORS)"));
        assert_done!(output_section_command("*"));

        assert_done!(statement_osc("ASSERT(SIZEOF(.upper)==0,\"Test\");"));
        assert_done!(output_section_command(
            "ASSERT(SIZEOF(.upper)==0,\"Test\");",
        ));
        assert_done!(output_section_command("FILL(0xff);"));

        assert_done!(output_sc("/DISCARD/ : { *(.note.GNU-stack) }"));
        assert_done!(output_sc(".DATA : { [A-Z]*(.data) }"));
        assert_done!(output_sc(".infoD     : {} > INFOD"));

        assert_done!(output_sc(".a:{*(.b .c)*(.d .e)}"));
    }
}