poe-superfilter 0.2.0

Extended syntax compiler for Path of Exile loot filters

use ast;

use std::sync::Arc;
use std::path::PathBuf;
use ast::BlockLevelNode;
use ast::block_statements as stm;
use ast::{Filter, Comment, AstLocation};
use ast::block::{Block, BlockType};
use ast::mixin::*;
use tok::Location as TokenLocation;
use tok::Tok;
use ast::import::ImportStatement;
use ast::var::{VarDefinition,VarReference};
use ast::expression::{ExpressionValue, ExpressionNode, ExpressionOperation};

grammar<'a>(file: &'a Arc<PathBuf>);

extern {
    type Location = TokenLocation;
    type Error = char;

    enum Tok {
        "(" => Tok::LParen,
        ")" => Tok::RParen,
        "-" => Tok::Minus,
        "+" => Tok::Plus,
        "*" => Tok::Times,
        "/" => Tok::Div,
        "," => Tok::Comma,
        "\n" => Tok::NewLine,
        "Show" => Tok::Show,
        "Hide" => Tok::Hide,
        "Mixin" => Tok::Mixin,
        "Import" => Tok::Import,
        ">=" => Tok::Gte,
        ">" => Tok::Gt,
        "<=" => Tok::Lte,
        "<" => Tok::Lt,
        "=" => Tok::Eql,
        "if" => Tok::If,
        "True" => Tok::True,
        "False" => Tok::False,
        Comment => Tok::Comment(<String>),
        BlockComment => Tok::BlockComment(<String>),
        Num => Tok::Num(<i64>),
        Float => Tok::Float(<f64>),
        QuotedStrLiteral => Tok::StrLiteral(<String>),
        Constant => Tok::Constant(<String>),
        VarIdentifier => Tok::VarIdentifier(<String>)
    }
}

pub Filter: Filter = {
    <l:@L> <defs:DefinitionBlock*> <first:InstructionBlock> <rest:AnyBlock*> <r:@R> => {
        let mut nodes = defs;
        nodes.push(first);
        nodes.extend(rest);
        Filter {
            nodes: nodes,
            location: AstLocation::new(l, r, file.clone())
        }
    },
    <l:@L> <defs:DefinitionBlock*> <r:@R> =>
        Filter {
            nodes: defs,
            location: AstLocation::new(l, r, file.clone())
        },
};

LineEndOrComment: Option<Comment> = {
    <s_option:Comment?> "\n" => {
        s_option.and_then(|comment_str| Some(Comment { content: comment_str, inline: true }))
    }
};

CommentLine: Comment = {
    <Comment> "\n" => Comment { content: <>, inline: false }
};

BlockCommentLine: Comment = {
    <BlockComment> "\n" => Comment { content: <>, inline: false }
};

ImportStmt: ImportStatement = {
    <l:@L> "Import" <path:RawStrLiteral> <r:@R> <comment:LineEndOrComment> =>
        ImportStatement {
            path: path,
            location: AstLocation::new(l, r, file.clone()),
            comment
        }
};

AnyBlock: BlockLevelNode = {
    <ImportStmt> => BlockLevelNode::Import(<>),
    InstructionBlock
};

DefinitionBlock: BlockLevelNode = {
    <VarDefinition> => BlockLevelNode::VarDef(<>),
    <ImportStmt> => BlockLevelNode::Import(<>),
    <CommentLine> => BlockLevelNode::Comment(<>),
};

InstructionBlock: BlockLevelNode = {
     <block_comments:BlockCommentLine*> <l:@L> <t:BlockType> <condition:("if" <ExpressionRoot>)?> <inline_comment:LineEndOrComment> <stmts:BlockStatement+> <r:@R> => BlockLevelNode::Block(
        Block {
            nodes: stmts,
            variant: t,
            location: AstLocation::new(l, r, file.clone()),
            condition: condition.and_then( |inner| Some(inner)),
            block_comments,
            inline_comment
        }
    ),
    <block_comments:BlockCommentLine*> <l:@L> "Mixin" <name:Constant> <args:("(" <Comma<VarIdentifier>> ")")?> <inline_comment:LineEndOrComment> <instructions:BlockStatement+> <r:@R> => {
        let params = args
            .unwrap_or(vec![])
            .iter()
            .map(|param_name| ast::mixin::Param { name: param_name.clone(), default: None })
            .collect();
        BlockLevelNode::Mixin(
            ast::mixin::Mixin {
                name: name,
                parameters: params,
                statements: instructions,
                location: AstLocation::new(l, r, file.clone()),
                block_comments,
                inline_comment
            }
        )
    }
};

BlockType: BlockType = {
    "Show" => BlockType::Show,
    "Hide" => BlockType::Hide
};

Comma<T>: Vec<T> = {
    <v:(<T> ",")*> <e:T?> => match e {
        None => v,
        Some(e) => {
            let mut v = v;
            v.push(e);
            v
        }
    }
};

VarDefinition: VarDefinition  = {
    <l:@L> <id:VarIdentifier> "=" <v:Value> <r:@R> <comment:LineEndOrComment> =>
        VarDefinition {
            identifier: id,
            values: v,
            location: AstLocation::new(l, r, file.clone()),
            comment
        }
};

VarReference: ExpressionValue = {
    <l:@L> <ident:VarIdentifier> <r:@R> => ExpressionValue::Var(
        VarReference {
            identifier: ident,
            location: AstLocation::new(l, r, file.clone())
        }
    )
};

SetValueStmt: stm::SetValueStatement = {
    <l:@L> <n:Constant> <val:Value> <r:@R> <comment:LineEndOrComment> =>
        stm::SetValueStatement {
            name: n,
            values: val,
            location: AstLocation::new(l, r, file.clone()),
            comment
        }
};

ConditionStmt: stm::ConditionStatement = {
    <l:@L> <n: Constant> <cond: StmCondition> <r:@R> <comment:LineEndOrComment> =>
        stm::ConditionStatement {
            name: n,
            condition: cond,
            location: AstLocation::new(l, r, file.clone()),
            comment
        }
};

StmCondition: stm::Condition = {
    <op:ComparisonOperator> <v:ExpressionRoot> => stm::Condition { value: v, operator: op }
};

MixinCall: MixinCall = {
    <l:@L> "+" <n:Constant> <params:("(" <Comma<Value>> ")")?> <r:@R> <comment:LineEndOrComment> =>
        MixinCall {
            name: n,
            parameters: params.unwrap_or(vec![]),
            location: AstLocation::new(l, r, file.clone()),
            comment
        }
};

#[inline]
BlockStatement: stm::BlockStatement = {
    <SetValueStmt> => stm::BlockStatement::SetValue(<>),
    <ConditionStmt> => stm::BlockStatement::Condition(<>),
    <MixinCall> => stm::BlockStatement::MixinCall(<>),
    <VarDefinition> => stm::BlockStatement::VarDef(<>),
    <CommentLine> => stm::BlockStatement::Comment(<>)
};

RawStrLiteral: String = {
    QuotedStrLiteral => String::from(<>),
    Constant => String::from(<>),
};

StrLiteral: ExpressionValue = {
    <l:@L> <s:RawStrLiteral> <r:@R> => ExpressionValue::String(s)
};

ComparisonOperator: stm::ComparisonOperator = {
    ">=" => stm::ComparisonOperator::Gte,
    ">" =>  stm::ComparisonOperator::Gt,
    "<=" => stm::ComparisonOperator::Lte,
    "<" =>  stm::ComparisonOperator::Lt,
    "=" =>  stm::ComparisonOperator::Eql
};

NumberLiteral: ExpressionValue = {
    <num:Num> => ExpressionValue::Int(num),
    <num:Float> => ExpressionValue::Decimal(num),
};

Bool: ExpressionValue = {
    "True" => ExpressionValue::Bool(true),
    "False" => ExpressionValue::Bool(false)
};

Value: Box<ExpressionNode> = {
    <l:@L> <values:ExpressionRoot+> <r:@R> => {
        if values.len() == 1 {
            values[0].clone()
        } else {
            Box::new(
                ExpressionNode::Val(ExpressionValue::List(values), AstLocation::new(l, r, file.clone()))
            )
        }
    }
};

ExprComparisonOp: ExpressionOperation = {
    ">=" => ExpressionOperation::Gte,
    ">" =>  ExpressionOperation::Gt,
    "<=" => ExpressionOperation::Lte,
    "<" =>  ExpressionOperation::Lt,
    "=" =>  ExpressionOperation::Eql
};

ExprSumOp: ExpressionOperation = {
    "+" => ExpressionOperation::Add,
    "-" => ExpressionOperation::Sub
};

ExprFactorOp: ExpressionOperation = {
    "*" => ExpressionOperation::Mul,
    "/" => ExpressionOperation::Div
};

Tier<Op,NextTier>: Box<ExpressionNode> = {
    Tier<Op,NextTier> Op NextTier => Box::new(ExpressionNode::Op(<>)),
    NextTier
};

ExpressionRoot = ComparisonTierExpression;
ComparisonTierExpression = Tier<ExprComparisonOp, SumTierExpression>;
SumTierExpression = Tier<ExprSumOp, FactorTierExpression>;
FactorTierExpression = Tier<ExprFactorOp, ExpressionTerm>;

ExpressionTerm: Box<ExpressionNode> = {
    <l:@L> <lit:ExpressionLiteral> <r:@R> => Box::new(ExpressionNode::Val(lit, AstLocation::new(l, r, file.clone()))),
    "(" <ExpressionRoot> ")" => <>
};

ExpressionLiteral: ExpressionValue = {
    VarReference,
    NumberLiteral,
    StrLiteral,
    Bool,
};

Color: ast::color::Color = {
    <r:ExpressionTerm> <g:ExpressionTerm> <b:ExpressionTerm> <a:ExpressionTerm> => ast::color::Color {
        r, g, b, a
    },
    <r:ExpressionTerm> <g:ExpressionTerm> <b:ExpressionTerm> <right:@R> => ast::color::Color {
        r,
        g,
        b,
        a: Box::new(
            ExpressionNode::Val(ExpressionValue::Int(255), AstLocation::new(right, right, file.clone())
        ))
    }
};