Sign in

    moonrego

    A Rego (Open Policy Agent) lexer, parser, evaluator and CLI for MoonBit.

    rego
    opa
    policy
    parser
    interpreter
    Download zip
    Author
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    yesterday
    Downloads
    15

    No dependencies

    #L76148/moonrego

    A Rego (Open Policy Agent) implementation for MoonBit: lexer, parser, evaluator and a command line tool.

    The project is developed one capability at a time. This release ships the lexer, parser, AST, value model and evaluator core; the built-in library, the eval command and the conformance corpus are next.

    #Why Rego

    Rego is the policy language of Open Policy Agent: Kubernetes admission control, Terraform and Conftest checks, and authorization in microservices all speak it. MoonBit already has Cedar (jaredzhou/mooncedar) and CEL (aloyscc/moon_cel), but moon search rego and moon search "opa policy" return no modules.

    #Scope

    • Lexer and parser for Rego modules: packages, imports, complete and partial rules, default, else, function rules, some, every, not, comprehensions and references.
    • Evaluator with virtual documents, partial sets and objects, and undefined semantics.
    • Built-ins for comparison, arithmetic, strings, arrays, objects, sets and type checks.
    • A CLI with eval, check and parse, plus a conformance corpus.

    Out of scope: bundles, decision logs, partial evaluation and the OPA server.

    #Command line

    moon run cmd/moonrego -- version moon run cmd/moonrego -- help

    #Quality gates

    moon check --target all --deny-warn moon fmt --check moon test --target wasm --deny-warn moon test --target wasm-gc --deny-warn

    #License

    Apache-2.0. Open Policy Agent is the semantic reference; no Go code is copied.

    EvalError

    pub(all) suberror EvalError {
    UnknownFunction(String)
    Unsupported(String)
    InvalidOperation(String)
    DivisionByZero
    RecursionLimit(String)
    Query(ParseError)
    } derive(
    Debug
    )

    An evaluation error. Undefined results are not errors: they are None.

    EvalError::message

    fn EvalError::message(self : EvalError) -> String

    A human readable explanation of the error.

    LexError

    pub(all) suberror LexError {
    UnexpectedChar(Position, Char)
    UnterminatedString(Position)
    UnterminatedRawString(Position)
    InvalidEscape(Position, Char)
    InvalidUnicodeEscape(Position)
    InvalidNumber(Position, String)
    } derive(
    Debug
    )

    A lexical error, always carrying the position where it was detected.

    LexError::message

    fn LexError::message(self : LexError) -> String

    A human readable explanation of the error.

    LexError::to_repr

    ParseError

    pub(all) suberror ParseError {
    UnexpectedToken(TokenKind, Position, String)
    UnexpectedEof(String)
    TrailingTokens(Position)
    Lexical(LexError)
    } derive(
    Debug
    )

    A syntax error, always carrying the position of the offending token.

    ParseError::message

    fn ParseError::message(self : ParseError) -> String

    A human readable explanation of the error.

    CompareOp

    pub(all) enum CompareOp {
    CmpEqual
    CmpNotEqual
    CmpLess
    CmpLessEqual
    CmpGreater
    CmpGreaterEqual
    } derive(Eq,
    Debug
    )

    A comparison operator inside an expression.
    impl Show for CompareOp

    CompareOp::equal

    fn CompareOp::equal(CompareOp, CompareOp) -> Bool

    CompareOp::from_token

    fn CompareOp::from_token(kind : TokenKind) -> CompareOp?

    The comparison operator for one token, or None when the token is not a comparison.

    CompareOp::not_equal

    fn CompareOp::not_equal(x : CompareOp, y : CompareOp) -> Bool

    CompareOp::output

    fn CompareOp::output(self : CompareOp, logger : &Logger) -> Unit

    CompareOp::to_string

    fn CompareOp::to_string(self : CompareOp) -> String

    ElseRule

    pub(all) struct ElseRule {
    value : Term?
    body : Array[Expr]
    position : Position
    } derive(Eq,
    Debug
    )

    One else branch of a rule.

    ElseRule::equal

    fn ElseRule::equal(ElseRule, ElseRule) -> Bool

    ElseRule::new

    fn ElseRule::new(value : Term?, body : Array[Expr], position : Position) -> ElseRule

    ElseRule::not_equal

    fn ElseRule::not_equal(x : ElseRule, y : ElseRule) -> Bool

    ElseRule::to_repr

    Evaluator

    pub(all) struct Evaluator {
    module_ : Module
    data : Value
    input : Value
    rule_depth : Int
    } derive(
    Debug
    )

    Evaluates one module against a data document and an input value.

    This increment covers complete, partial set and partial object rules, default, else, variable binding, references into data and input, iteration with some and every, not, with input as, comprehensions and the arithmetic and set operators. Function rules and other built-ins arrive in later increments.

    Evaluator::new

    fn Evaluator::new(module_ : Module, data : Value, input : Value) -> Evaluator

    Evaluator::query

    fn Evaluator::query(self : Evaluator, term : Term) -> Value? raise EvalError

    Evaluates one term. None means undefined.

    Evaluator::query_text

    fn Evaluator::query_text(self : Evaluator, source : String) -> Value? raise EvalError

    Parses a term as a query and evaluates it. None means undefined.

    Expr

    pub(all) struct Expr {
    kind : ExprKind
    position : Position
    } derive(Eq,
    Debug
    )

    One expression in a rule body.

    Expr::assign

    fn Expr::assign(target : Term, value : Term, position : Position) -> Expr

    Expr::compare

    fn Expr::compare(op : CompareOp, left : Term, right : Term, position : Position) -> Expr

    Expr::equal

    fn Expr::equal(Expr, Expr) -> Bool

    Expr::every

    fn Expr::every(vars : Array[Term], collection : Term, body : Array[Expr], position : Position) -> Expr

    Expr::new

    fn Expr::new(kind : ExprKind, position : Position) -> Expr

    Expr::not

    fn Expr::not(body : Array[Expr], position : Position) -> Expr

    Expr::not_equal

    fn Expr::not_equal(x : Expr, y : Expr) -> Bool

    Expr::some_in

    fn Expr::some_in(vars : Array[Term], collection : Term, position : Position) -> Expr

    Expr::some_vars

    fn Expr::some_vars(vars : Array[Term], position : Position) -> Expr

    Expr::term

    fn Expr::term(term : Term, position : Position) -> Expr

    Expr::to_repr

    Expr::unify

    fn Expr::unify(target : Term, value : Term, position : Position) -> Expr

    Expr::with_modifiers

    fn Expr::with_modifiers(expr : Expr, modifiers : Array[WithModifier], position : Position) -> Expr

    ExprKind

    pub(all) enum ExprKind {
    Term(Term)
    Assign(Term, Term)
    Unify(Term, Term)
    Compare(CompareOp, Term, Term)
    SomeVars(Array[Term])
    SomeIn(Array[Term], Term)
    Every(Array[Term], Term, Array[Expr])
    Not(Array[Expr])
    With(Expr, Array[WithModifier])
    } derive(Eq,
    Debug
    )

    The shape of one expression.

    ExprKind::equal

    fn ExprKind::equal(ExprKind, ExprKind) -> Bool

    ExprKind::not_equal

    fn ExprKind::not_equal(x : ExprKind, y : ExprKind) -> Bool

    ExprKind::to_repr

    Import

    pub(all) struct Import {
    path : Array[String]
    alias_name : String?
    position : Position
    } derive(Eq,
    Debug
    )

    One import declaration. path keeps every segment, so import data.foo.bar as baz is ["data", "foo", "bar"] with alias baz.

    Import::equal

    fn Import::equal(Import, Import) -> Bool

    Import::new

    fn Import::new(path : Array[String], alias_name : String?, position : Position) -> Import

    Import::not_equal

    fn Import::not_equal(x : Import, y : Import) -> Bool

    Import::to_repr

    Module

    pub(all) struct Module {
    package_path : Array[String]
    imports : Array[Import]
    rules : Array[Rule]
    position : Position
    } derive(Eq,
    Debug
    )

    One parsed Rego module: its package, imports and rules.

    Module::equal

    fn Module::equal(Module, Module) -> Bool

    Module::new

    fn Module::new(package_path : Array[String], imports : Array[Import], rules : Array[Rule], position : Position) -> Module

    Module::not_equal

    fn Module::not_equal(x : Module, y : Module) -> Bool

    Module::to_repr

    Position

    pub(all) struct Position {
    offset : Int
    line : Int
    column : Int
    } derive(Eq,
    Debug
    )

    A position in the source text. offset counts characters from the start of the file, while line and column are one-based and meant for humans.

    Position::equal

    fn Position::equal(Position, Position) -> Bool

    Position::new

    fn Position::new(offset : Int, line : Int, column : Int) -> Position

    Position::not_equal

    fn Position::not_equal(x : Position, y : Position) -> Bool

    Position::to_repr

    RefStep

    pub(all) enum RefStep {
    Field(String)
    Index(Term)
    } derive(Eq,
    Debug
    )

    One step of a reference: a field access or an index expression.

    RefStep::equal

    fn RefStep::equal(RefStep, RefStep) -> Bool

    RefStep::field

    fn RefStep::field(name : String) -> RefStep

    RefStep::index

    fn RefStep::index(term : Term) -> RefStep

    RefStep::not_equal

    fn RefStep::not_equal(x : RefStep, y : RefStep) -> Bool

    RefStep::to_repr

    Rule

    pub(all) struct Rule {
    kind : RuleKind
    name : String
    args : Array[String]
    key : Term?
    value : Term?
    body : Array[Expr]
    is_default : Bool
    else_rules : Array[ElseRule]
    position : Position
    } derive(Eq,
    Debug
    )

    One rule, including its head, optional value, body and else branches.

    An empty body means the rule is a fact. A None value means the rule produces true when its body holds, as in allow if { ... }.

    Rule::complete

    fn Rule::complete(name : String, value : Term?, body : Array[Expr], position : Position) -> Rule

    A complete rule: name := value or name if { body }.

    Rule::equal

    fn Rule::equal(Rule, Rule) -> Bool

    Rule::function

    fn Rule::function(name : String, args : Array[String], value : Term?, body : Array[Expr], position : Position) -> Rule

    A function rule: name(args) := value if { body }.

    Rule::not_equal

    fn Rule::not_equal(x : Rule, y : Rule) -> Bool

    Rule::partial_object

    fn Rule::partial_object(name : String, key : Term, value : Term?, body : Array[Expr], position : Position) -> Rule

    A partial object rule: name[key] := value if { body }.

    Rule::partial_set

    fn Rule::partial_set(name : String, element : Term, body : Array[Expr], position : Position) -> Rule

    A partial set rule: name contains element if { body }.

    Rule::to_repr

    RuleKind

    pub(all) enum RuleKind {
    Complete
    PartialSet
    PartialObject
    Function
    } derive(Eq,
    Debug
    )

    The shape of a rule head.
    impl Show for RuleKind

    RuleKind::equal

    fn RuleKind::equal(RuleKind, RuleKind) -> Bool

    RuleKind::not_equal

    fn RuleKind::not_equal(x : RuleKind, y : RuleKind) -> Bool

    RuleKind::output

    fn RuleKind::output(self : RuleKind, logger : &Logger) -> Unit

    RuleKind::to_repr

    RuleKind::to_string

    fn RuleKind::to_string(self : RuleKind) -> String

    Term

    pub(all) enum Term {
    NullTerm
    BoolTerm(Bool)
    NumberTerm(String)
    StringTerm(String)
    Var(String)
    Ref(Term, Array[RefStep])
    ArrayLit(Array[Term])
    ObjectLit(Array[(Term, Term)])
    SetLit(Array[Term])
    Call(Term, Array[Term])
    ArrayComprehension(Array[Expr], Term)
    SetComprehension(Array[Expr], Term)
    ObjectComprehension(Array[Expr], Term, Term)
    } derive(Eq,
    Debug
    )

    A term: a value-producing piece of syntax.

    Numbers keep their literal text, because Rego numbers are arbitrary precision and the evaluator decides how to represent them. A leading minus is folded into the literal by the parser.

    Term::array_comprehension

    fn Term::array_comprehension(body : Array[Expr], value : Term) -> Term

    Term::array_lit

    fn Term::array_lit(items : Array[Term]) -> Term

    Term::boolean

    fn Term::boolean(value : Bool) -> Term

    Term::call

    fn Term::call(name : String, args : Array[Term]) -> Term

    Term::call_term

    fn Term::call_term(callee : Term, args : Array[Term]) -> Term

    Term::equal

    fn Term::equal(Term, Term) -> Bool

    Term::not_equal

    fn Term::not_equal(x : Term, y : Term) -> Bool

    Term::null

    fn Term::null() -> Term

    Term::number

    fn Term::number(literal : String) -> Term

    Term::object_comprehension

    fn Term::object_comprehension(body : Array[Expr], key : Term, value : Term) -> Term

    Term::object_lit

    fn Term::object_lit(pairs : Array[(Term, Term)]) -> Term

    Term::reference

    fn Term::reference(base : Term, steps : Array[RefStep]) -> Term

    Term::set_comprehension

    fn Term::set_comprehension(body : Array[Expr], value : Term) -> Term

    Term::set_lit

    fn Term::set_lit(items : Array[Term]) -> Term

    Term::string

    fn Term::string(value : String) -> Term

    Term::to_repr

    Term::variable

    fn Term::variable(name : String) -> Term

    Token

    pub(all) struct Token {
    kind : TokenKind
    text : String
    position : Position
    } derive(Eq,
    Debug
    )

    A token and where it came from.

    text is the meaningful text: the identifier or keyword as written, the numeric literal, the decoded value of a string, the contents of a raw string, or the operator itself.

    Token::equal

    fn Token::equal(Token, Token) -> Bool

    Token::new

    fn Token::new(kind : TokenKind, text : String, position : Position) -> Token

    Token::not_equal

    fn Token::not_equal(x : Token, y : Token) -> Bool

    Token::to_repr

    TokenKind

    pub(all) enum TokenKind {
    Eof
    Ident
    NumberLit
    StringLit
    RawStringLit
    Package
    Import
    As
    Default
    Else
    Not
    SomeKeyword
    Every
    In
    If
    Contains
    With
    TrueKeyword
    FalseKeyword
    NullKeyword
    LBrace
    RBrace
    LBracket
    RBracket
    LParen
    RParen
    Comma
    Colon
    Semicolon
    Dot
    Pipe
    Ampersand
    Assign
    Equal
    EqualEqual
    NotEqual
    Less
    LessEqual
    Greater
    GreaterEqual
    Plus
    Minus
    Star
    Slash
    Percent
    } derive(Eq,
    Debug
    )

    The lexical categories of Rego.

    The variants that share a name with a built-in constructor or a core type carry a suffix, so SomeKeyword and StringLit cannot be confused with Some or String.

    TokenKind::equal

    fn TokenKind::equal(TokenKind, TokenKind) -> Bool

    TokenKind::name

    fn TokenKind::name(self : TokenKind) -> String

    A short name used in error messages and tests.

    TokenKind::not_equal

    fn TokenKind::not_equal(x : TokenKind, y : TokenKind) -> Bool

    Value

    pub(all) enum Value {
    NullVal
    BoolVal(Bool)
    IntVal(Int64)
    FloatVal(Double)
    StringVal(String)
    ArrayVal(Array[Value])
    ObjectVal(Array[(Value, Value)])
    SetVal(Array[Value])
    } derive(Eq,
    Debug
    )

    A Rego value.

    Integers are kept as Int64 and other numbers as Double; arbitrary precision decimals arrive in a later increment. Objects and sets are flat arrays with linear lookup, which lets any term act as a key without imposing a hashable representation.
    impl Show for Value

    Value::array

    fn Value::array(items : Array[Value]) -> Value

    Value::boolean

    fn Value::boolean(value : Bool) -> Value

    Value::compare

    fn Value::compare(self : Value, other : Value) -> Int?

    Orders two values that Rego can compare, or returns None when the types do not form an ordering.

    Value::element

    fn Value::element(self : Value, target : Value) -> Bool

    Whether a set contains an element.

    Value::equal

    fn Value::equal(Value, Value) -> Bool

    Value::equals

    fn Value::equals(self : Value, other : Value) -> Bool

    Rego equality: integers and floats compare by numeric value, sets compare regardless of order, and objects compare by key.

    Value::float

    fn Value::float(value : Double) -> Value

    Value::from_json

    fn Value::from_json(json : Json) -> Value

    Converts JSON to a value.

    Numbers that are integral and within the safe integer range become IntVal; other numbers stay FloatVal.

    Value::int

    fn Value::int(value : Int64) -> Value

    Value::length

    fn Value::length(self : Value) -> Int

    The number of elements: array and set items, object entries, or string characters. Scalars have length zero.

    Value::lookup

    fn Value::lookup(self : Value, key : Value) -> Value?

    Looks up a key in an object, or returns None for other values.

    Value::not_equal

    fn Value::not_equal(x : Value, y : Value) -> Bool

    Value::null

    fn Value::null() -> Value

    Value::number_literal

    fn Value::number_literal(text : String) -> Value

    Builds a number from a Rego literal. A literal with a fraction or an exponent becomes a Double; an integer that fits becomes an Int64.

    Value::object

    fn Value::object(pairs : Array[(Value, Value)]) -> Value

    Value::output

    fn Value::output(self : Value, logger : &Logger) -> Unit

    Value::set

    fn Value::set(items : Array[Value]) -> Value

    A set with duplicate elements removed.

    Value::string

    fn Value::string(value : String) -> Value

    Value::to_json

    fn Value::to_json(self : Value) -> Json

    Converts a value to JSON.

    Sets become arrays, which is what Rego does when it serialises a set to JSON. Object keys that are not strings are rendered with to_rego_text.

    Value::to_rego_text

    fn Value::to_rego_text(self : Value) -> String

    Renders the value the way Rego prints it: strings are quoted, sets use braces, and objects keep their insertion order.

    Value::to_repr

    Value::to_string

    fn Value::to_string(self : Value) -> String

    Value::with_element

    fn Value::with_element(self : Value, item : Value) -> Value

    Returns a set with the element added once.

    Value::with_entry

    fn Value::with_entry(self : Value, key : Value, value : Value) -> Value

    Returns an object with the key set to the value, replacing an existing entry.

    WithModifier

    pub(all) struct WithModifier {
    target : Term
    value : Term
    } derive(Eq,
    Debug
    )

    One with target as value modifier.

    WithModifier::equal

    WithModifier::new

    fn WithModifier::new(target : Term, value : Term) -> WithModifier

    WithModifier::not_equal

    fn WithModifier::not_equal(x : WithModifier, y : WithModifier) -> Bool

    is_ident_continue

    fn is_ident_continue(c : Char) -> Bool

    Whether a character may continue an identifier.

    is_ident_start

    fn is_ident_start(c : Char) -> Bool

    Whether a character may start an identifier.

    keyword_kind

    fn keyword_kind(text : String) -> TokenKind?

    The keyword kind for one identifier, or None for a plain identifier.

    parse_body

    fn parse_body(source : String) -> Array[Expr] raise ParseError

    Parses a statement body without its surrounding braces and requires the whole input to be consumed.

    Statements are separated by ; or by a line break. The body may contain value expressions, some, every, not and with modifiers.

    parse_expr

    fn parse_expr(source : String) -> Expr raise ParseError

    Parses one value expression and requires the whole input to be consumed.

    The expression is a term, a comparison, an assignment (:=) or a unification (=). The operators are lowered to the Rego built-ins plus, minus, mul, div, rem, intersection and union.

    parse_json_value

    fn parse_json_value(source : String) -> Value raise
    ParseError

    Parses JSON text into a value.

    parse_module

    fn parse_module(source : String) -> Module raise ParseError

    Parses one Rego module: its package, imports and rules.

    parse_term

    fn parse_term(source : String) -> Term raise ParseError

    Parses one term including the infix operators, and requires the whole input to be consumed.

    Supported: scalars, variables, references with field and index steps, calls, arrays, objects, sets, parentheses and the arithmetic and set operators. Comprehensions and statement keywords arrive later.

    tokenize

    fn tokenize(source : String) -> Array[Token] raise LexError

    Splits a Rego source text into tokens, ending with one Eof token.

    Comments and whitespace are skipped. String tokens carry their decoded value; raw string tokens carry their contents verbatim.

    version

    fn version() -> String

    The library version reported by the command line tool.