brainstem 0.1.0

A Brainfuck compiler and interpreter library, with a BrainStem frontend language.
Documentation
use std::str::FromStr;
use crate::brain_stem::ast::{Expression, Statement};

grammar;


match {
    r"[0-9]+" => NUMBER,
    r"[a-zA-Z_]\w*" => ID,
    r#""[^"]*""# => STRING,
    "'[^']'" => CHAR,

    _, 

    // Skip whitespace and comments
    r"\s*" => { },
    r"//[^\n\r]*[\n\r]*" => { }, // `// comment`
    r"/\*([^\*]*\*+[^\*/])*([^\*]*\*+|[^\*])*\*/" => { }, // `/* comment */`
}

pub Program : Vec<Statement<'input>> = {
    <Statement*>,
}

Statement: Statement<'input> = {
    "{" <s:Statement+> "}" => Statement::Block(s),
    VarOrArrayDeclaration,
    "if" <c:Expression> "then" "{" <t:Statement*> "}" <e:("else" "{" <Statement*> "}")?> => 
        Statement::If(c, Box::new(Statement::Block(t)), e.map(Statement::Block).map(Box::new)),
    "putc" "(" <Expression> ")" ";" => Statement::PutChar(<>),
    "while" <c:Expression> "{" <b:Statement*> "}" => Statement::While(c, Box::new(Statement::Block(b))),
    <Expression> ";" => Statement::Expression(<>),
    "PushStackFrame" "(" ")" ";" => Statement::PushStackFrame(Vec::new()),
    "PushStackFrame" "(" <mut h:(<Identifier> "=" <Expression> ",")*> <tn:Identifier> "=" <tv:Expression> ")" ";" => {
        h.push((tn, tv));
        Statement::PushStackFrame(h)
    },
    "PopStackFrame" "(" ")" ";" => Statement::PopStackFrame,
    "MoveToStackFrameBelow" "(" <Identifier> ")" ";" => Statement::MoveToStackFrameBelow(<>),
};

VarOrArrayDeclaration: Statement<'input> = {
    "var" <n:Identifier> "=" <i: Expression> ";" => Statement::VarDeclaration(n, i),
    "var" <n:Identifier> "[" "]" "=" "[" <mut i: (<Expression> ",")*> <t:Expression> "]" ";" => {
        i.push(t);
        Statement::ArrayDeclaration(n, i)
    },
    "var" <n:Identifier> "[" "]" "=" <i:StringLiteral> ";" =>
        Statement::ArrayDeclaration(n, i.chars().map(|c| Expression::Literal(c as i32)).collect()),
};

StringLiteral: &'input str = {
    <l:STRING> => l[1..l.len() -1].into()
}

Expression: Expression<'input> = {
    #[precedence(level="0")] // Highest precedence
    Primary,

    #[precedence(level="1")] #[assoc(side="left")]
    "!" <e:Expression> => Expression::Not(Box::new(e)),
    <o:"-"> <e:Expression> => Expression::Binary(Box::new(Expression::Literal(0)), o.into(), Box::new(e)),
    <o:"+"> <e:Expression> => e,

    #[precedence(level="2")] #[assoc(side="left")]
    <l:Expression> <o:"*"> <r:Expression> => Expression::Binary(Box::new(l), o.into(), Box::new(r)),
    <l:Expression> <o:"/"> <r:Expression> => Expression::Binary(Box::new(l), o.into(), Box::new(r)),
    <l:Expression> <o:"%"> <r:Expression> => Expression::Binary(Box::new(l), o.into(), Box::new(r)),

    #[precedence(level="3")] #[assoc(side="left")]
    <l:Expression> <o:"+"> <r:Expression> => Expression::Binary(Box::new(l), o.into(), Box::new(r)),
    <l:Expression> <o:"-"> <r:Expression> => Expression::Binary(Box::new(l), o.into(), Box::new(r)),

    #[precedence(level="4")] #[assoc(side="left")]
    <l:Expression> "<" <r:Expression> => Expression::Binary(Box::new(l), "<".into(), Box::new(r)),
    <l:Expression> "<=" <r:Expression> => Expression::Binary(Box::new(l), "<=".into(), Box::new(r)),
    // Swap r and l to implement gt and ge as lt and le.
    <l:Expression> ">" <r:Expression> => Expression::Binary(Box::new(r), "<".into(), Box::new(l)),
    <l:Expression> ">=" <r:Expression> => Expression::Binary(Box::new(r), "<=".into(), Box::new(l)),

    #[precedence(level="5")] #[assoc(side="left")]
    <l:Expression> <o:"!="> <r:Expression> => Expression::Binary(Box::new(l), o.into(), Box::new(r)),
    <l:Expression> <o:"=="> <r:Expression> => Expression::Binary(Box::new(l), o.into(), Box::new(r)),

    #[precedence(level="6")] #[assoc(side="left")]
    <l:Expression> <o:"&&"> <r:Expression> => Expression::Binary(Box::new(l), o.into(), Box::new(r)),

    #[precedence(level="7")] #[assoc(side="left")]
    <l:Expression> <o:"||"> <r:Expression> => Expression::Binary(Box::new(l), o.into(), Box::new(r)),

    #[precedence(level="8")] #[assoc(side="left")]
    <n:Identifier> <i:("[" <Expression> "]")?> "=" <v:Expression> => match i {
        Some(index) => Expression::ArrayAssignment(n, Box::new(index), Box::new(v)),
        None => Expression::Assignment(n, Box::new(v)),
    },
    "LINMEM" "[" <a:Expression> "]" "=" <v:Expression> => Expression::MemoryWrite(Box::new(a), Box::new(v)),
}

Primary: Expression<'input> = {
    NUMBER => Expression::Literal(i32::from_str(<>).unwrap()),
    STRING => { 
        assert_eq!(<>.len(), 3);
        Expression::Literal(<>.chars().nth(1).unwrap() as i32)
    },
    <n:Identifier> <i:("[" <Expression> "]")?> => match i {
        None => Expression::Variable(n),
        Some(index) => Expression::ArrayLookup(n, Box::new(index))
    },
    "LINMEM" "[" <Expression> "]" => Expression::MemoryRead(Box::new(<>)), 
    "(" <Expression> ")",
};

Identifier = ID;