polar-core 0.23.0

Polar core library for oso, an open source policy engine for authorization that’s embedded in your application
Documentation
use std::str::FromStr;
use std::collections::BTreeMap;

use crate::lexer::{self, Token};
use crate::parser::Line;
use crate::error;
use crate::terms::*;
use crate::rules::*;
use crate::terms::*;
use crate::numerics::*;
use crate::resource_block;
use super::ValueOrLogical;

use lalrpop_util::ParseError;

grammar(src_id: u64);

extern {
    type Location = usize;
    type Error = error::ParseError;

    enum Token {
        "Integer" => lexer::Token::Integer(<i64>),
        "Float" => lexer::Token::Float(<f64>),
        "String" => lexer::Token::String(<String>),
        "Boolean" => lexer::Token::Boolean(<bool>),
        "Symbol" => lexer::Token::Symbol(<Symbol>),
        ":" => lexer::Token::Colon,         // :
        "," => lexer::Token::Comma,         // ,
        "[" => lexer::Token::LB,            // [
        "]" => lexer::Token::RB,            // ]
        "(" => lexer::Token::LP,            // (
        ")" => lexer::Token::RP,            // )
        "{" => lexer::Token::LCB,           // {
        "}" => lexer::Token::RCB,           // }
        "." => lexer::Token::Dot,           // .
        "new" => lexer::Token::New,         // new
        "!" => lexer::Token::Bang,          // !
        "*" => lexer::Token::Mul,           // *
        "/" => lexer::Token::Div,           // /
        "mod" => lexer::Token::Mod,         // mod
        "rem" => lexer::Token::Rem,         // rem
        "+" => lexer::Token::Add,           // +
        "-" => lexer::Token::Sub,           // -
        "==" => lexer::Token::Eq,           // ==
        "!=" => lexer::Token::Neq,          // !=
        "<=" => lexer::Token::Leq,          // <=
        ">=" => lexer::Token::Geq,          // >=
        "<" => lexer::Token::Lt,            // <
        ">" => lexer::Token::Gt,            // >
        "=" => lexer::Token::Unify,         // =
        ":=" => lexer::Token::Assign,       // :=
        "|" => lexer::Token::Pipe,          // |
        ";" => lexer::Token::SemiColon,     // ;
        "?=" => lexer::Token::Query,        // ?=
        "cut" => lexer::Token::Cut,         // cut
        "debug" => lexer::Token::Debug,     // debug
        "print" => lexer::Token::Print,     // print
        "in" => lexer::Token::In,           // in
        "forall" => lexer::Token::ForAll,   // forall
        "if" => lexer::Token::If,           // if
        "and" => lexer::Token::And,         // and
        "or" => lexer::Token::Or,           // or
        "not" => lexer::Token::Not,         // not
        "matches" => lexer::Token::Matches, // matches
        "type" => lexer::Token::Type,       // type
    }
}

ResWord: String = {
  "type" => "type".to_owned(),
  "cut" => "cut".to_owned(),
  "debug" => "debug".to_owned(),
  "print" => "print".to_owned(),
  "in" => "in".to_owned(),
  "forall" => "forall".to_owned(),
  "if" => "if".to_owned(),
  "and" => "and".to_owned(),
  "or" => "or".to_owned(),
  "not" => "not".to_owned(),
  "new" => "new".to_owned(),
  "matches" => "matches".to_owned(),
}



// ****** Values ******* //

Integer: i64 = {
    <"Integer">,
"+" <"Integer">,
"-" <i:"Integer"> => -i,
}

Float: f64 = {
    <"Float">,
"+" <"Float">,
"-" <f:"Float"> => -f,
}


Number: Value = {
    <Integer> => Value::Number(<>.into()),
    <Float> => Value::Number(<>.into()),
};


PolarString: Value = <s:"String"> => {
    Value::String(s)
};

Boolean: Value = <b:"Boolean"> => {
    Value::Boolean(b)
};

Name: Symbol = <s:"Symbol"> => s;

Variable: Value  = <n:Name> => {
    Value::Variable(n)
};

RestVar: Value  = "*" <n:Name> => {
    Value::RestVariable(n)
};

Call: Value = {
    // No args.
    <name:Name> "("  ")" => {
        let args = vec![];
        let kwargs = None;
        Value::Call(Call{name, args, kwargs})
    },
    // Positional args only.
    <name:Name> "(" <mut args:(<ValExp> ",")*> <arg:ValExp> ")" => {
        args.push(arg);
        let kwargs = None;
        Value::Call(Call{name, args, kwargs})
    },
    // Positional args + kwargs.
    <name:Name> "(" <mut args:(<ValExp> ",")*> <fields:(<Kwargs<ValExp>>)>")" => {
        let kwargs = Some(fields);
        Value::Call(Call{name, args, kwargs})
    }
};

DotCall: Value = {
  <Call>,
  // No args.
  <w:ResWord> "("  ")" => {
      let args = vec![];
      let kwargs = None;
      let name = Symbol(w);
      Value::Call(Call{name, args, kwargs})
  },
  // Positional args only.
  <w:ResWord> "(" <mut args:(<ValExp> ",")*> <arg:ValExp> ")" => {
      args.push(arg);
      let kwargs = None;
      let name = Symbol(w);
      Value::Call(Call{name, args, kwargs})
  },
  // Positional args + kwargs.
  <w:ResWord> "(" <mut args:(<ValExp> ",")*> <fields:(<Kwargs<ValExp>>)>")" => {
      let kwargs = Some(fields);
      let name = Symbol(w);
      Value::Call(Call{name, args, kwargs})
  },
}

New: Value = {
    "new" <call:Spanned<Call>> => {
        let args = vec![call];
        let op = Operation{operator: Operator::New, args};
        Value::Expression(op)
    },
};

Field<T>: (Symbol, Term) = {
    <name:Name> ":" <value:T> => (name, value),
    <w:ResWord> ":" <value:T> => (Symbol(w), value),
    <name:Spanned<Variable>> => (name.value().as_symbol().unwrap().clone(), name),
}

Fields<T>: BTreeMap<Symbol, Term> = {
    <field:Field<T>> => {
        let mut fields = BTreeMap::new();
        fields.insert(field.0, field.1);
        fields
    },
    <loc:@L> <mut fields:Fields<T>> "," <tail:Field<T>?> =>? match tail {
        None => Ok(fields),
        Some((name, value)) => {
            let existing = fields.insert(name.clone(), value);
            if existing.is_some() {
                return Err(ParseError::User { error: error::ParseError::DuplicateKey { loc, key: name.0 } })
            }
            Ok(fields)
        }
    }
};

Kwarg<T>: (Symbol, Term) = {
    <name:Name> ":" <value:T> => (name, value),
    <w:ResWord> ":" <value:T> => (Symbol(w), value),
}

Kwargs<T>: BTreeMap<Symbol, Term> = {
    <field:Kwarg<T>> => {
        let mut fields = BTreeMap::new();
        fields.insert(field.0, field.1);
        fields
    },
    <loc:@L> <mut fields:Kwargs<T>> "," <tail:Kwarg<T>?> =>? match tail {
        None => Ok(fields),
        Some((name, value)) => {
            let existing = fields.insert(name.clone(), value);
            if existing.is_some() {
                return Err(ParseError::User { error: error::ParseError::DuplicateKey { loc, key: name.0 } })
            }
            Ok(fields)
        }
    }
};

Object<T>: Dictionary = {
    "{" <fields:Fields<T>> "}" => {
        Dictionary { fields }
    },
    "{" "}" => {
        Dictionary {
            fields: BTreeMap::new()
        }
    }
};

// ****** Dicts and literals ******* //

DictionaryTerm: Value = <fields:Object<ExpectValue<Exp5<"Term">>>> => {
    Value::Dictionary(fields)
};
// Pattern dictionaries cannot contain any operators.
DictionaryPattern: Value = <fields:Object<ExpectValue<Exp9<"Pattern">>>> => {
    Value::Pattern(Pattern::Dictionary(fields))
};

InstanceLiteralPattern: Value = <tag:Name> <fields:Object<ExpectValue<Exp9<"Pattern">>>> => {
    let instance = InstanceLiteral{tag, fields};
    Value::Pattern(Pattern::Instance(instance))
};

// ****** Operations ******* //

BuiltinOperator: Operator = {
    "debug" => Operator::Debug,
    "print" => Operator::Print,
};


BuiltinOperation: Value = {
    <op:BuiltinOperator> "(" <mut args:(<ValExp> ",")*> <arg:ValExp?> ")" => {
        match arg {
            Some(arg) => args.push(arg),
            None => ()
        };
        Value::Expression(Operation{operator: op, args: args})
    },
    "cut" => {
        let args = vec![];
        let op = Operation{operator: Operator::Cut, args};
        Value::Expression(op)
    },
    "forall" "(" <arg1:LogExp> "," <arg2:LogExp> ")" => {
        let args = vec![arg1, arg2];
        let op = Operation{operator: Operator::ForAll, args};
        Value::Expression(op)
    },
};

RewritableOperator: Operator = {
    "." => Operator::Dot,
    "new" => Operator::New,
    "in" => Operator::In,
};

RewrittenOperation: Value = {
    <op:RewritableOperator> "(" <mut args:(<TermExp> ",")*> <arg:TermExp?> ")" => {
        match arg {
            Some(arg) => args.push(arg),
            None => ()
        };
        Value::Expression(Operation{operator: op, args: args})
    },
};


// ****** Expressions ******* //

// All ExpN & Exp productions are macros with one parameter. The parameter is the
// *string* "Term" or "Pattern" which controls whether the expression is over terms
// or patterns.  (It is a string since we need to conditionally
// change the expression precedence allowed in patterns versus terms depending
// on the parameter type, and LALRPOP does not allow conditional macros on anything
// other than a string.

// Any term expression
TermExp: Term = {
    <t:Exp1<"Term">> => match t {
        ValueOrLogical::Value(t) | ValueOrLogical::Logical(t) | ValueOrLogical::Either(t) => {
            t
        }
    }
}

// A value expression
ValExp: Term = {
    <ExpectValue<Exp1<"Term">>>
}

// A logical expression
LogExp: Term = {
    <ExpectLogical<Exp1<"Term">>>
}

// `ValueOrLogic` is used to do some simple parse-time
// checks for correctly formed expressions. For example,
// `x in y` is a logical expression, so it does
// not make sense to compare the result with a value.
// `x in y > 0` fails to parse because `A > B` expects
// A and B to be values, but `x in y` is a logical term.

// In Prolog, logicals would be a callable - things that can be called and fail or success
// Values are not callable. Together, values and logicals are terms.


IsValue<T>: ValueOrLogical = {
    <Spanned<T>> => ValueOrLogical::Value(<>)
}

IsLogical<T>: ValueOrLogical = {
    <Spanned<T>> => ValueOrLogical::Logical(<>)
}

IsAny<T>: ValueOrLogical = {
    <Spanned<T>> => ValueOrLogical::Either(<>)
}


ExpectValue<T>: Term = {
    <loc:@L> <term:T> =>? {
        match term {
            ValueOrLogical::Logical(term) => {
                Err(ParseError::User { error: error::ParseError::WrongValueType { loc, term, expected: "value".to_string() } })
            },
            ValueOrLogical::Value(t) | ValueOrLogical::Either(t) => Ok(t)
        }
    }
}

ExpectLogical<T>: Term = {
    <loc:@L> <term:T> =>? {
        match term {
            ValueOrLogical::Value(term) => {
                Err(ParseError::User { error: error::ParseError::WrongValueType { loc, term, expected: "logical expression".to_string() } })
            },
            ValueOrLogical::Logical(t) | ValueOrLogical::Either(t) => Ok(t)
        }
    }
}

Exp10<T>: ValueOrLogical = {
    <IsValue<Pattern>> if T == "Pattern",
    <Value> if T == "Term",
    "(" <Exp1<T>> ")", // "resets" the parsing
}

CallTerm: Value = {
    <DotCall>,
    <w:ResWord> => Value::String(w),
    <s:"Symbol"> => Value::String(s.0),
    // These provide ways to get keys that aren't
    // expressable as `foo.bar`
    "(" <Variable> ")",
    "(" <PolarString> ")",
}

DotOp<T>: Value = {
    <head:ExpectValue<Exp9<T>>> "." <call_term:Spanned<CallTerm>> => {
        let args = vec![head, call_term];
        let op = Operation{operator: Operator::Dot, args};
        Value::Expression(op)
    },
}

// .
Exp9<T>: ValueOrLogical = {
    <IsAny<DotOp<T>>>,
    <Exp10<T>>,
};

// in
InExp<T>: Value = {
    <left:ExpectValue<Exp8<T>>> "in" <right:ExpectValue<Exp9<T>>> => {
        let args = vec![left, right];
        let op = Operation{operator: Operator::In, args};
        Value::Expression(op)
    },
}

Matches = {"matches"};
// matches
MatchExp<T>: Value = {
    // Symbols on the RHS are treated as class names, just like in a specializers
    <left:ExpectValue<Exp8<T>>> Matches <right:Spanned<Pattern>> => {
        let right = if let Value::Variable(ref sym) = right.value() {
            right.clone_with_value(Value::Pattern(Pattern::Instance(InstanceLiteral {
                tag: sym.clone(),
                fields: Dictionary::new()
            })))
        } else {
            right
        };
        let args = vec![left, right];
        let op = Operation{operator: Operator::Isa, args};
        Value::Expression(op)
    },
}

Exp8<T>: ValueOrLogical = {
    <IsLogical<InExp<T>>>,
    <IsLogical<MatchExp<T>>>,
    <Exp9<T>>,
}

// * / mod rem
Op7: Operator = {
    "*" => Operator::Mul,
    "/" => Operator::Div,
    "mod" => Operator::Mod,
    "rem" => Operator::Rem,
}

MulExp<T>: Value = {
    <exp7:ExpectValue<Exp7<T>>> <operator:Op7> <exp8:ExpectValue<Exp8<T>>> => {
        let args = vec![exp7, exp8];
        let op = Operation{operator, args};
        Value::Expression(op)
    },
}
Exp7<T>: ValueOrLogical = {
    <IsValue<MulExp<T>>>,
    <Exp8<T>>,
}

// + -
Op6: Operator = {
    "+" => Operator::Add,
    "-" => Operator::Sub,
}
AddExp<T>: Value = {
    <exp6:ExpectValue<Exp6<T>>> <operator:Op6> <exp7:ExpectValue<Exp7<T>>> => {
        let args = vec![exp6, exp7];
        let op = Operation{operator, args};
        Value::Expression(op)
    },
}

Exp6<T>: ValueOrLogical = {
    <IsValue<AddExp<T>>>,
    <Exp7<T>>,
}

// == != <= < >= >
Op5: Operator = {
    "==" => Operator::Eq,
    "!=" => Operator::Neq,
    "<=" => Operator::Leq,
    ">=" => Operator::Geq,
    "<" => Operator::Lt,
    ">" => Operator::Gt,
}

CmpExp<T>: Value = {
    <exp5:ExpectValue<Exp5<T>>> <operator:Op5> <exp6:ExpectValue<Exp6<T>>> => {
        let args = vec![exp5, exp6];
        let op = Operation{operator, args};
        Value::Expression(op)
    },
}

Exp5<T>: ValueOrLogical = {
    <IsLogical<CmpExp<T>>>,
    <Exp6<T>>,
}

// =, :=
UnifyExp<T>: Value = {
    <exp4:ExpectValue<Exp4<T>>> "=" <exp5:ExpectValue<Exp5<T>>> => {
        let args = vec![exp4, exp5];
        let op = Operation{operator: Operator::Unify, args};
        Value::Expression(op)
    },
    <variable:Spanned<Variable>> ":=" <exp5:ExpectValue<Exp5<T>>> => {
        let args = vec![variable, exp5];
        let op = Operation{operator: Operator::Assign, args};
        Value::Expression(op)
    },
}

Exp4<T>: ValueOrLogical = {
    <IsLogical<UnifyExp<T>>>,
    <Exp5<T>>,
}



 // !
Not = {"not"};
NotExp<T>: Value = {
    Not <exp4:ExpectLogical<Exp4<T>>> => {
        let args = vec![exp4];
        let op = Operation{operator: Operator::Not, args};
        Value::Expression(op)
    },
}

Exp3<T>: ValueOrLogical = {
    <IsLogical<NotExp<T>>>,
    <Exp4<T>>,
}


And = {"and"};
AndExp<T>: Value = {
    <head:ExpectLogical<Exp3<T>>> And <mut tail:ExpectLogical<Exp2<T>>> => {
        let args = match &mut tail.value() {
            Value::Expression(Operation{operator: Operator::And, args: tail_args}) => {
                let mut args = vec![head];
                args.append(&mut tail_args.clone());
                args
            }
            _ => {
                vec![head, tail]
            }
        };
        let op = Operation{operator: Operator::And, args};
        Value::Expression(op)
    },
}

Exp2<T>: ValueOrLogical = {
    <IsLogical<AndExp<T>>>,
    <Exp3<T>>,
}


Or = {"or"};
OrExp<T>: Value = {
    <head:ExpectLogical<Exp2<T>>> Or <mut tail:ExpectLogical<Exp1<T>>> => {
        let args = match &mut tail.value() {
            Value::Expression(Operation{operator: Operator::Or, args: tail_args}) => {
                let mut args = vec![head];
                args.append(&mut tail_args.clone());
                args
            }
            _ => {
                vec![head, tail]
            }
        };
        let op = Operation{operator: Operator::Or, args};
        Value::Expression(op)
    },
}

Exp1<T>: ValueOrLogical = {
    <IsLogical<OrExp<T>>>,
    <Exp2<T>>,
}


ListTerms<T>: Vec<Term> = {
    <ExpectValue<Exp6<T>>> => vec![<>],
    <mut list:ListTerms<T>> "," <tail:ExpectValue<Exp6<T>>?> => {
        match tail {
            None => list,
            Some(tail) => {
                list.push(tail);
                list
            }
        }
    },
}

List<T>: Value = {
    "[" "]" => Value::List(vec![]),
    "[" <Spanned<RestVar>> "]" => Value::List(vec![<>]),
    "[" <ListTerms<T>> "]" => Value::List(<>),
    "[" <mut terms:ListTerms<T>> "," <rest:Spanned<RestVar>> "]" => {
        terms.push(rest);
        Value::List(terms)
    }
}

Pattern: Value = {
    <Number>,
    <PolarString>,
    <Boolean>,
    <Variable>,
    <DictionaryPattern>,
    <InstanceLiteralPattern>,
    <List<"Pattern">>,
};

Value: ValueOrLogical = {
    <IsLogical<BuiltinOperation>>,
    <IsAny<Boolean>>,
    <IsAny<Variable>>,
    <IsLogical<Call>>,
    <IsValue<New>>,
    <IsValue<List<"Term">>>,
    <IsValue<Number>>,
    <IsValue<PolarString>>,
    <IsValue<DictionaryTerm>>,
    <IsLogical<RewrittenOperation>>,
};



// ****** Terms + Patterns ******* //

Spanned<T>: Term = <start:@L> <value:T> <end:@R> =>  Term::new_from_parser(src_id, start, end, value);

pub Term = TermExp;



// ****** Rules + Lines ******* //

ParameterList: Vec<Parameter> = {
    <param:Parameter> => vec![param],
    <mut list:ParameterList> "," <param:Parameter> => {
        list.push(param);
        list
    },
};


Parameter: Parameter = {
    <parameter:ExpectValue<Exp6<"Term">>> => {
        Parameter{parameter, specializer: None}
    },
    <parameter:Spanned<Variable>> ":" <specializer:Spanned<Pattern>> => {
        if let Value::Variable(class_name) = specializer.value() {
            let fields = BTreeMap::new();
            let instance_literal = InstanceLiteral{tag: class_name.clone(), fields: Dictionary{fields}};
            Parameter {
                parameter,
                specializer: Some(specializer.clone_with_value(Value::Pattern(Pattern::Instance(instance_literal)))),
            }
        } else {
            Parameter{parameter, specializer: Some(specializer)}
        }
    },
};


RuleHead: (Symbol, Vec<Parameter>) = {
    <name:Name> "(" ")" => {
        (name, vec![])
    },
    <name:Name> "(" <params:ParameterList> ")" => {
        (name, params)
    }
};

Define = {"if"};

BodilessRule: Rule = <start_head:@L> <head:RuleHead> <start:@L> <end:@R> ";" => {
    let (name, params) = head;
    let op = Operation{operator: Operator::And, args: vec![]};
    let body = Term::new_from_parser(src_id, start, end, Value::Expression(op));
    Rule::new_from_parser(src_id, start_head, start, name, params, body)
};

Rule: Rule = {
    <BodilessRule>,
    <start_head:@L> <head:RuleHead> <end_head:@R> Define <body:TermExp> ";" => {
        let (name, params) = head;
        let body = match body.value() {
            Value::Expression(Operation{operator: Operator::And, ..}) => {
                body
            },
            _ => {
                let op = Operation{operator: Operator::And, args: vec![body.clone()]};
                body.clone_with_value(Value::Expression(op))
            }
        };
        Rule::new_from_parser(src_id, start_head, end_head, name, params, body)
    }
}

RuleType: Rule = "type" <BodilessRule>;

pub(crate) Rules: Vec<Rule> = <Rule*>;

// TODO(gj): combine this with ListTerms/List?
StringListTerms: Vec<Term> = {
    <Spanned<PolarString>> => vec![<>],
    <mut list:StringListTerms> "," <tail:Spanned<PolarString>?> => {
        if let Some(tail) = tail {
            list.push(tail);
        }
        list
    },
}
StringList: Value = {
    "[" "]" => Value::List(vec![]),
    "[" <StringListTerms> "]" => Value::List(<>),
}
DeclarationValue: Value = {
    <StringList> => <>,
    <Object<Spanned<Variable>>> => Value::Dictionary(<>),
};
Declaration: resource_block::Production = <Spanned<Variable>> "=" <Spanned<DeclarationValue>> ";" => resource_block::Production::Declaration((<>));

OnRelation: (Term, Term) = <Spanned<Variable>> <Spanned<PolarString>> => (<>);
ShorthandRuleBody: (Term, Option<(Term, Term)>) = <implier:Spanned<PolarString>> <relation:OnRelation?> ";" => (<>);
ShorthandRule: resource_block::Production = <head:Spanned<PolarString>> Define <body:ShorthandRuleBody> => resource_block::Production::ShorthandRule(<>);

ResourceBlockProduction: resource_block::Production = {
    <Declaration> => <>,
    <ShorthandRule> => <>,
};

ResourceBlockProductions: Vec<resource_block::Production> = <ResourceBlockProduction*>;

Line: Line = {
    <Rule> => Line::Rule(<>),
    <RuleType> => Line::RuleType(<>),
    "?=" <TermExp> ";" => Line::Query(<>),

    <start:@L> <keyword:Spanned<Variable>?> <resource:Variable> "{" <productions:ResourceBlockProductions> "}" <end:@R> => {
        let resource = Term::new_from_parser(src_id, start, end, resource);
        Line::ResourceBlock { keyword, resource, productions }
    }
}

pub Lines: Vec<Line> = <Line*>;