nb2nl 0.2.0

A small crate for translating NetsBlox code into Netlogo source.
Documentation
use super::*;
use lalrpop_util::ParseError;

grammar;

extern {
    type Error = AstError;
}

match {
    r";[^@][^\n\r]*" => {},
    r"\s+" => {},
    r"create-ordered-[\p{alpha}.?=*!<>:#+/%_^'&-][\p{alpha}\p{digit}.?=*!<>:#+/%_^'&-]*" => "CREATE-ORDERED-IDENT",
    r"-?[0-9]+(\.[0-9]*)?([eE][+-]?[0-9]+)?" => "NUMBER",
} else {
    r"create-[\p{alpha}.?=*!<>:#+/%_^'&-][\p{alpha}\p{digit}.?=*!<>:#+/%_^'&-]*" => "CREATE-IDENT",
} else {
    r"[\p{alpha}.?=*!<>:#+/%_^'&-][\p{alpha}\p{digit}.?=*!<>:#+/%_^'&-]*-own" => "IDENT-OWN",
} else { _ }

pub Program = Item*;

RawUnaryFunc = {
    "forward", "fd", "right", "rt", "left", "lt",
    "random", "random-float",
    "any?", "one-of", "other",
    "distance", "towards",
    "min", "max", "sum", "mean",
    "abs", "sin", "cos", "tan", "asin", "acos", "sqrt", "ln", "log", "floor", "ceiling",
    "user-input",
};
UnaryFunc: Ident = <l:@L> <name:RawUnaryFunc> <r:@R> => Ident { id: clean_ident(name), raw_span: Span(l, r) };

RawBinaryFunc = {
    "setxy", "distancexy", "towardsxy",
    "subtract-headings",
    "atan",
};
BinaryFunc: Ident = <l:@L> <name:RawBinaryFunc> <r:@R> => Ident { id: clean_ident(name), raw_span: Span(l, r) };

Item: Item = {
    Globals => Item::Globals(<>),
    Breed => Item::Breed(<>),
    Own => Item::Own(<>),
    Function => Item::Function(<>),
};

Globals: Globals = <annotations:Annotation*> <l:@L> "globals" "[" <idents:Ident*> "]" <r:@R> => Globals { annotations, idents, raw_span: Span(l, r) };
Breed: Breed = <l:@L> "breed" "[" <plural:Ident> <singular:Ident> "]" <r:@R> => Breed { plural, singular, raw_span: Span(l, r) };
Own: Own = {
    <l:@L> <owner:"IDENT-OWN"> "[" <props:Ident*> "]" <r:@R> => {
        Own { plural_owner: Ident { id: owner[..owner.len()-4].to_lowercase(), raw_span: Span(l, l + owner.len() - 4) }, props, raw_span: Span(l, r) }
    },
};
Function: Function = {
    <annotations:Annotation*> <l:@L> "to" <name:Ident> <stmts:Stmt*> "end" <r:@R> => {
        Function { annotations, name, params: vec![], reports: false, stmts, raw_span: Span(l, r) }
    },
    <annotations:Annotation*> <l:@L> "to" <name:Ident> "[" <params:Ident*> "]" <stmts:Stmt*> "end" <r:@R> => {
        Function { annotations, name, params, reports: false, stmts, raw_span: Span(l, r) }
    },
    <annotations:Annotation*> <l:@L> "to-report" <name:Ident> <stmts:Stmt*> "end" <r:@R> => {
        Function { annotations, name, params: vec![], reports: true, stmts, raw_span: Span(l, r) }
    },
    <annotations:Annotation*> <l:@L> "to-report" <name:Ident> "[" <params:Ident*> "]" <stmts:Stmt*> "end" <r:@R> => {
        Function { annotations, name, params, reports: true, stmts, raw_span: Span(l, r) }
    },
};

Stmt: Stmt = {
    Report => Stmt::Report(<>),
    IfElse => Stmt::IfElse(<>),
    FnCall => Stmt::FnCall(<>),
    Ident => Stmt::FnCall(FnCall { name: <>, args: vec![] }),
    VarDecl => Stmt::VarDecl(<>),
    Assign => Stmt::Assign(<>),
    Loop => Stmt::Loop(<>),
    Repeat => Stmt::Repeat(<>),
    While => Stmt::While(<>),
    Create => Stmt::Create(<>),
    Ask => Stmt::Ask(<>),
    Hatch => Stmt::Hatch(<>),
    "(" <Stmt> ")" => <>,
};

Report: Report = <l:@L> "report" <value:Expr> => Report { value, lspan: l };
IfElse: IfElse = {
    <l:@L> "if" <condition:Expr> "[" <then:Stmt*> "]" <r:@R> => IfElse { condition, then, otherwise: None, raw_span: Span(l, r) },
    <l:@L> "ifelse" <condition:Expr> "[" <then:Stmt*> "]" "[" <otherwise:Stmt*> "]" <r:@R> => IfElse { condition, then, otherwise: Some(otherwise), raw_span: Span(l, r) },
};
VarDecl: VarDecl = <l:@L> "let" <name:Ident> <value:Expr> => VarDecl { name, value, lspan: l };
Assign: Assign = <l:@L> "set" <name:Ident> <value:Expr> => Assign { name, value, lspan: l };
Loop: Loop = <l:@L> "loop" "[" <stmts:Stmt*> "]" <r:@R> => Loop { stmts, raw_span: Span(l, r) };
Repeat: Repeat = <l:@L> "repeat" <count:Expr> "[" <stmts:Stmt*> "]" <r:@R> => Repeat { count, stmts, raw_span: Span(l, r) };
While: While = <l:@L> "while" "[" <condition:Expr> "]" "[" <stmts:Stmt*> "]" <r:@R> => While { condition, stmts, raw_span: Span(l, r) };
Create: Create = <l:@L> <h:CreateHelper> <count:Expr> "[" <stmts:Stmt*> "]" <r:@R> => {
    Create { breed_plural: h.0, ordered: h.1, count, stmts, raw_span: Span(l, r) }
};
Ask: Ask = <l:@L> "ask" <agents:Expr> "[" <stmts:Stmt*> "]" <r:@R> => Ask { agents, stmts, raw_span: Span(l, r) };
Hatch: Hatch = <l:@L> "hatch" <count:Expr> "[" <stmts:Stmt*> "]" <r:@R> => Hatch { count, stmts, raw_span: Span(l, r) };

CreateHelper: (Ident, bool) = {
    <l:@L> <raw:"CREATE-ORDERED-IDENT"> => (Ident { id: raw[15..].to_lowercase(), raw_span: Span(l + 15, l + raw.len()) }, true),
    <l:@L> <raw:"CREATE-IDENT"> => (Ident { id: raw[7..].to_lowercase(), raw_span: Span(l + 7, l + raw.len()) }, false),
};

Expr: Expr = {
    <l:@L>"ifelse-value" <cond:Logic> "[" <a:Expr> "]" "[" <b:Expr> "]" <r:@R> => {
        Expr::Choice { condition: Box::new(cond), a: Box::new(a), b: Box::new(b), raw_span: Span(l, r) }
    },
    Logic,
};
Logic: Expr = { // and/or/xor have the same precedence in netlogo - this is not a mistake
    <a:Logic> "and" <b:CmpEq> => Expr::And { a: Box::new(a), b: Box::new(b) },
    <a:Logic> "or" <b:CmpEq> => Expr::Or { a: Box::new(a), b: Box::new(b) },
    <a:Logic> "xor" <b:CmpEq> => Expr::Xor { a: Box::new(a), b: Box::new(b) },
    CmpEq,
};
CmpEq: Expr = {
    <a:CmpEq> "=" <b:Cmp> => Expr::Equ { a: Box::new(a), b: Box::new(b) },
    <a:CmpEq> "!=" <b:Cmp> => Expr::Neq { a: Box::new(a), b: Box::new(b) },
    Cmp,
};
Cmp: Expr = {
    <a:Cmp> "<" <b:Sum> => Expr::Less { a: Box::new(a), b: Box::new(b) },
    <a:Cmp> "<=" <b:Sum> => Expr::LessEq { a: Box::new(a), b: Box::new(b) },
    <a:Cmp> ">" <b:Sum> => Expr::Great { a: Box::new(a), b: Box::new(b) },
    <a:Cmp> ">=" <b:Sum> => Expr::GreatEq { a: Box::new(a), b: Box::new(b) },
    Sum,
};
Sum: Expr = {
    <a:Sum> "+" <b:Product> => Expr::Add { a: Box::new(a), b: Box::new(b) },
    <a:Sum> "-" <b:Product> => Expr::Sub { a: Box::new(a), b: Box::new(b) },
    Product,
};
Product: Expr = {
    <a:Product> "*" <b:Power> => Expr::Mul { a: Box::new(a), b: Box::new(b) },
    <a:Product> "/" <b:Power> => Expr::Div { a: Box::new(a), b: Box::new(b) },
    <a:Product> "mod" <b:Power> => Expr::Mod { a: Box::new(a), b: Box::new(b) },
    Power,
}
Power: Expr = {
    <a:Power> "^" <b:Term> => Expr::Pow { a: Box::new(a), b: Box::new(b) },
    Term,
};
Term: Expr = {
    <l:@L> "[" <expr:Expr> "]" "of" <target:Molecule> => Expr::Fetch { target: Box::new(target), expr: Box::new(expr), lspan: l },
    <l:@L> "min-one-of" <agents:Expr> "[" <expr:Expr> "]" <r:@R> => Expr::MinMaxOneOf { agents: Box::new(agents), expr: Box::new(expr), is_max: false, raw_span: Span(l, r) },
    <l:@L> "max-one-of" <agents:Expr> "[" <expr:Expr> "]" <r:@R> => Expr::MinMaxOneOf { agents: Box::new(agents), expr: Box::new(expr), is_max: true, raw_span: Span(l, r) },
    <l:@L> "not" <val:Molecule> => Expr::Not { val: Box::new(val), lspan: l },
    "(" <l:@L> "-" <val:Molecule> ")" => Expr::Neg { val: Box::new(val), lspan: l },
    FnCall => Expr::FnCall(<>),
    Molecule,
};
Molecule: Expr = {
    <agents:Molecule> "in-radius" <radius:Atom> => Expr::InRadius { agents: Box::new(agents), radius: Box::new(radius) },
    Atom
};
Atom: Expr = {
    Value => Expr::Value(<>),
    "(" <Expr> ")" => <>,
};

FnCall: FnCall = {
    "(" <name:Ident> <args:Term+> ")" => FnCall { name, args },
    <name:UnaryFunc> <arg:Term> => FnCall { name, args: vec![arg] },
    <name:BinaryFunc> <arg1:Term> <arg2:Term> => FnCall { name, args: vec![arg1, arg2] },
};

Value: Value = {
    Ident => Value::Ident(<>),
    Number => Value::Number(<>),
    Text => Value::Text(<>),
    List => Value::List(<>),
};

Ident: Ident = <l:@L> <v:r"[\p{alpha}.?=*!<>:#+/%_^'&-][\p{alpha}\p{digit}.?=*!<>:#+/%_^'&-]*"> <r:@R> => Ident { id: clean_ident(v), raw_span: Span(l, r) };
Number: Number = <l:@L> <v:"NUMBER"> <r:@R> => Number { value: String::from(v), raw_span: Span(l, r) };
Text: Text = <l:@L> <v:r#""(\\[\\nrt"']|[^\\"\r\n])*""#> <r:@R> => Text { content: clean_string(v), raw_span: Span(l, r) };
List: List = <l:@L> "(" "list" <values:Expr*> ")" <r:@R> => List { values, raw_span: Span(l, r) };

// -------------------------------------- //
// -- annotation stuff (meta-langauge) -- //
// -------------------------------------- //

Annotation: Annotation = {
    <l:@L> ";@guivar" <ident:Ident> <value:Expr> => Annotation::GuiVar(GuiVar { ident, value, lspan: l }),
    <l:@L> ";@placein" <sprite:Ident> <x:Number> <y:Number> <comment:Text?> <r:@R> =>? {
        let x = match x.value.parse() { Ok(v) => v, Err(_) => return Err(ParseError::User { error: AstError::ParseFloat { problem_span: x.span() } }) };
        let y = match y.value.parse() { Ok(v) => v, Err(_) => return Err(ParseError::User { error: AstError::ParseFloat { problem_span: y.span() } }) };
        Ok(Annotation::PlaceIn(PlaceIn { sprite, x, y, comment, raw_span: Span(l, r) }))
    },
};