diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..7983516
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,22 @@
+[package]
+name = "rego-rs"
+version = "0.1.0"
+edition = "2021"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[dependencies]
+anyhow = "1.0.66"
+ordered-float = "3.4.0"
+serde = {version = "1.0.150", features = ["derive", "rc"] }
+serde_json = "1.0.89"
+log = "0.4.17"
+env_logger="0.10.0"
+
+[dev-dependencies]
+serde_yaml = "0.9.16"
+test-generator = "0.3.1"
+walkdir = "2.3.2"
+
+[build-dependencies]
+anyhow = "1.0.66"
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000..02bcc5e
--- /dev/null
+++ b/build.rs
@@ -0,0 +1,10 @@
+// Copyright (c) Rego-Rs Authors.
+// Licensed under the Apache 2.0 license.
+use anyhow::Result;
+
+fn main() -> Result<()> {
+ // Copy hooks to appropriate location so that git will run them.
+ std::fs::copy("./scripts/pre-commit", "./.git/hooks/pre-commit")?;
+ std::fs::copy("./scripts/pre-push", "./.git/hooks/pre-push")?;
+ Ok(())
+}
diff --git a/docs/grammar.md b/docs/grammar.md
new file mode 100644
index 0000000..fd1598c
--- /dev/null
+++ b/docs/grammar.md
@@ -0,0 +1,174 @@
+
+```
+module: package imports { rule }
+
+package: "package" path-ref
+
+imports: { "import" path-ref [ "as" var ] }
+
+path-ref: path-ref NO_WS "." NO_WS IDENT
+| path-ref NO_WS "[" STRING "]"
+| IDENT
+
+rule: default-rule
+| spec-rule
+
+default-rule: "default" rule-ref assign-op term
+
+spec-rule: rule-head rule-bodies
+
+rule-head: func-rule
+| contains-rule
+| object-rule
+| set-rule
+| compr-rule
+
+func-rule: rule-ref "(" term { "," term } [","] ")" [ rule-assign ]
+
+contains-rule: rule-ref "contains" or-expr
+
+object-rule: rule-ref NO_WS "[" membership-expr "]" rule-assign
+
+set-rule: rule-ref NO_WS "[" membership-expr "]"
+
+compr-rule: rule-ref [ rule-assign ]
+
+rule-ref: rule-ref NO_WS "." NO_WS var
+| path-ref NO_WS "[" membership-expr "]"
+| var
+
+rule-assign: assign-op membership-expr
+
+rule-bodies: "if" "{" query "}" alternatives
+| "if" literal-stmt alternatives
+| "{" query "}" alternatives
+
+alternatives: query-blocks
+| else-blocks
+
+query-blocks: { "{" query "}" }
+
+else-blocks: { else-block }
+
+else-block: "else" [rule-assign] "if" "{" query "}"
+| "else" [rule-assign] "if" literal-stmt
+| "else" [rule-assign] "{" query "}"
+
+assign-op: "=" | ":="
+
+query: literal-stmt { sep literal-stmt }
+
+sep: ";" | "\n" | "\r\n"
+
+literal-stmt: literal with-modifiers
+
+with-modifiers: { "with" path-ref "as" in-expr }
+
+literal: some
+| every
+| expr
+| not-expr
+
+some: some-vars
+| some-in
+
+some-vars: "some" var { "," var }
+
+some-in: "some" ref [ "," ref ] "in"
+
+every: "every" var [ "," var ] "in" bool-expr "{" query "}"
+
+expr: assign-expr
+
+not-expr: "not" assign-expr
+
+assign-expr: ref assign-op membership-expr
+
+membership-expr: membership-expr "in" bool-expr
+| bool-expr "," bool-expr
+| bool-expr
+
+in-expr: in-expr "in" bool-expr
+| bool-expr
+
+bool-expr: bool-expr bool-op or-expr
+| or-expr
+
+bool-op: "<" | "<=" | "==" | ">=" | ">" | "!="
+
+or-expr: or-expr "|" and-expr
+| and-expr
+
+and-expr: and-expr "&" arith-expr
+| arith-expr
+
+arith-expr: arith-expr ("+" | "-") mul-div-expr
+| mul-div-expr
+
+mul-div-expr: mul-div-expr ("*" | "/") term
+| term
+
+term: ref
+
+ref: scalar-or-var
+| compr-set-or-object
+| compr-or-array
+| unary-expr
+| parens-expr
+| ref-dot
+| ref-brack
+| call-expr
+
+ref-dot: ref NO_WS "." NO_WS var
+
+ref-brack: ref NO_WS "[" in-expr "]"
+
+call-expr: path-ref NO_WS "(" call-args [","] ")"
+
+call-args: in-expr { "," in-expr }
+
+parens-expr: "(" membership-expr ")"
+
+unary-expr: "-" in-expr
+
+compr-set-or-object: set-compr
+| set
+| object-compr
+| object
+
+set-compr: "{" compr "}"
+
+# Set must have at least one item.
+set: "{" in-expr { "," in-expr } [","] "}"
+| "set(" ")" # empty set
+
+object: "{" field { "," field } [","] "}"
+| "{" "}" # empty object
+
+field: in-expr ":" in-expr
+
+# Comprehension or array
+compr-or-array: array-compr
+| array
+
+array-compr: "[" compr "]"
+
+array: "[" in-expr { "," in-expr } [","] "]"
+| "[" "]" # Empty array
+
+# Comprehension
+compr: ref "|" query
+
+scalar-or-var: var
+| NUMBER
+| STRING
+| RAWSTRING
+| "null"
+| "true"
+| "false"
+
+var: IDENT
+| non-imported-future-keyword
+
+non-imported-future-future-keyword: "contains" | "every" | "if" | "in"
+```
diff --git a/scripts/coverage b/scripts/coverage
new file mode 100755
index 0000000..458f898
--- /dev/null
+++ b/scripts/coverage
@@ -0,0 +1,49 @@
+#!/bin/bash
+# Copyright (c) Rego-Rs Authors.
+# Licensed under the Apache 2.0 license.
+
+set -e
+
+if ! command -v grcov > /dev/null; then
+ cargo install grcov
+fi
+
+if ! command -v llvm-profdata > /dev/null; then
+ rustup component add llvm-tools-preview
+fi
+
+#export LLVM_PROFILE_FILE='target/cargo-test-%p-%m.profraw'
+#export CARGO_INCREMENTAL=1
+#export RUSTFLAGS='-Cinstrument-coverage'
+
+echo "Building with instrumentation"
+cargo build --all-targets
+
+if [ "$1" == "--no-run" ]; then
+ exit 0
+fi
+
+# Remove existing coverage information.
+rm -f target/*.profraw
+rm -rf target/coverage
+mkdir -p target/coverage
+
+echo "Running tests"
+cargo test
+
+# Generate html
+grcov target/ --binary-path ./target/x86_64-unknown-linux-musl/debug/deps -s src/ -t html \
+--branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/html
+
+if [ "$1" == "--show" ]; then
+ echo "Opening report in browser"
+ xdg-open target/coverage/html/src/index.html 2>/dev/null
+ echo "Done"
+fi
+
+# Generate markdown
+grcov target/ --binary-path ./target/x86_64-unknown-linux-musl/debug/deps -s src/ -t markdown \
+--branch --ignore-not-existing --ignore '../*' --ignore "/*" -o target/coverage/markdown
+cat target/coverage/markdown
+
+#TODO: Maybe use coveralls format (json) and query data to lockdown code coverage.
diff --git a/scripts/make-docs b/scripts/make-docs
new file mode 100755
index 0000000..b8ace5a
--- /dev/null
+++ b/scripts/make-docs
@@ -0,0 +1,15 @@
+#!/bin/bash
+# Copyright (c) Rego-Rs Authors.
+# Licensed under the Apache 2.0 license.
+
+git stash
+cargo doc --no-deps
+git checkout docs
+rm -rf docs
+cp -r target/x86_64-unknown-linux-musl/doc ./docs
+echo "" > docs/index.html
+git add docs
+git commit -s
+git push
+git checkout -
+git stash pop
diff --git a/scripts/pre-commit b/scripts/pre-commit
new file mode 100755
index 0000000..0897847
--- /dev/null
+++ b/scripts/pre-commit
@@ -0,0 +1,25 @@
+#!/bin/bash
+# Copyright (c) Rego-Rs Authors.
+# Licensed under the Apache 2.0 license.
+
+set -eo pipefail
+
+if [ -f Cargo.toml ]; then
+ # Ensure that all targets can be built.
+ scripts/coverage --no-run
+
+ #Ensure that code is correctly formatted.
+ cargo fmt --check || (echo "Run cargo fmt to fix formatting" && exit 1)
+
+ # Ensure that clippy warnings are addressed.
+ cargo clippy --all-targets --no-deps -- -Dwarnings
+
+ # Ensure that all modifications are included.
+ # TODO refine status checking.
+ if git status -s | grep -e "MM " -e "??" -e "AM " -e " M " > /dev/null; then
+ printf "\nUnstaged changes found:\n"
+ git status -s | grep -e "MM " -e "??" -e "AM " -e " M "
+ echo "Stage them and try again"
+ exit 1
+ fi
+fi
diff --git a/scripts/pre-push b/scripts/pre-push
new file mode 100755
index 0000000..e851671
--- /dev/null
+++ b/scripts/pre-push
@@ -0,0 +1,15 @@
+#!/bin/bash
+# Copyright (c) Rego-Rs Authors.
+# Licensed under the Apache 2.0 license.
+
+set -eo pipefail
+
+if [ -f Cargo.toml ]; then
+ # Run precommit checks
+ dir=$(dirname "${BASH_SOURCE[0]}")
+ "$dir/pre-commit"
+
+ # Ensure that all tests pass
+ # Also generate coverage information.
+ scripts/coverage
+fi
diff --git a/scripts/rego-eval b/scripts/rego-eval
new file mode 100755
index 0000000..1883402
--- /dev/null
+++ b/scripts/rego-eval
@@ -0,0 +1,13 @@
+#!/bin/bash
+# Copyright (c) Rego-Rs Authors.
+# Licensed under the Apache 2.0 license.
+
+set -e
+rego=$(realpath -e $1)
+
+if [ ! -z "$2" ]; then
+ input=$(realpath -e $2)
+ cargo test interpreter::one_file -- --include-ignored --nocapture "$rego" "$input"
+else
+ cargo test interpreter::one_file -- --include-ignored --nocapture "$rego"
+fi
diff --git a/scripts/rego-lex b/scripts/rego-lex
new file mode 100755
index 0000000..d08296e
--- /dev/null
+++ b/scripts/rego-lex
@@ -0,0 +1,27 @@
+#!/bin/bash
+# Copyright (c) Rego-Rs Authors.
+# Licensed under the Apache 2.0 license.
+
+set -e
+
+usage="usage: rego-lex [-v]"
+
+if [ -z "$1" ]; then
+ echo "$usage"
+ exit 1
+fi
+
+rego=$(realpath -e $1)
+
+case "$2" in
+ "-v")
+ verbose="verbose"
+ ;;
+ *)
+ if [ ! -z "$2" ]; then
+ echo "$usage"
+ exit 1
+ fi
+esac
+
+eval "cargo test lexer::one_file -- --include-ignored --nocapture $rego $verbose"
diff --git a/scripts/rego-parse b/scripts/rego-parse
new file mode 100755
index 0000000..b4d0e2f
--- /dev/null
+++ b/scripts/rego-parse
@@ -0,0 +1,8 @@
+#!/bin/bash
+# Copyright (c) Rego-Rs Authors.
+# Licensed under the Apache 2.0 license.
+
+set -e
+
+rego=$(realpath -e $1)
+cargo test parser::one_file -- --include-ignored --nocapture "$rego"
diff --git a/scripts/yaml-test-eval b/scripts/yaml-test-eval
new file mode 100755
index 0000000..80aff37
--- /dev/null
+++ b/scripts/yaml-test-eval
@@ -0,0 +1,8 @@
+#!/bin/bash
+# Copyright (c) Rego-Rs Authors.
+# Licensed under the Apache 2.0 license.
+
+set -e
+yaml=$(realpath -e $1)
+
+RUST_BACKTRACE=1 cargo test interpreter::one_yaml -- --include-ignored --nocapture "$yaml"
diff --git a/scripts/yaml-test-parse b/scripts/yaml-test-parse
new file mode 100755
index 0000000..ca10ffe
--- /dev/null
+++ b/scripts/yaml-test-parse
@@ -0,0 +1,8 @@
+#!/bin/bash
+# Copyright (c) Rego-Rs Authors.
+# Licensed under the Apache 2.0 license.
+
+set -e
+yaml=$(realpath -e $1)
+
+RUST_BACKTRACE=1 cargo test parser::one_yaml -- --include-ignored --nocapture "$yaml"
diff --git a/snippets/2.rego b/snippets/2.rego
new file mode 100644
index 0000000..5f30ffb
--- /dev/null
+++ b/snippets/2.rego
@@ -0,0 +1,23 @@
+Cpackage play
+
+a := {4}
+
+mydoc(x) := path {
+ path := "data.play.a"
+}
+
+x := [ y |
+ y := data.play.a | data.play.b with data.play.a as {5} with data.play.b as {6}
+]
+
+r := [ m | m := data.play.p with data.play.p as 5 + 6; true ]
+
+
+allow {
+ input.x
+ == 5
+
+ input.y == 5
+ input.y
+ == 5
+}
\ No newline at end of file
diff --git a/src/ast.rs b/src/ast.rs
new file mode 100644
index 0000000..141b791
--- /dev/null
+++ b/src/ast.rs
@@ -0,0 +1,284 @@
+// Copyright (c) Rego-Rs Authors.
+// Licensed under the Apache 2.0 license.
+
+use crate::lexer::*;
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
+pub enum BinOp {
+ And,
+ Or,
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
+pub enum ArithOp {
+ Add,
+ Sub,
+ Mul,
+ Div,
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
+pub enum BoolOp {
+ Lt,
+ Le,
+ Eq,
+ Ge,
+ Gt,
+ Ne,
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
+pub enum AssignOp {
+ Eq,
+ ColEq,
+}
+
+#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
+pub enum Expr<'source> {
+ // Simple items that only have a span as content.
+ String(Span<'source>),
+ RawString(Span<'source>),
+ Number(Span<'source>),
+ True(Span<'source>),
+ False(Span<'source>),
+ Null(Span<'source>),
+ Var(Span<'source>),
+
+ // array
+ Array {
+ span: Span<'source>,
+ items: Vec>,
+ },
+
+ // set
+ Set {
+ span: Span<'source>,
+ items: Vec>,
+ },
+
+ Object {
+ span: Span<'source>,
+ fields: Vec<(Span<'source>, Expr<'source>, Expr<'source>)>,
+ },
+
+ // Comprehensions
+ ArrayCompr {
+ span: Span<'source>,
+ term: Box>,
+ query: Query<'source>,
+ },
+
+ SetCompr {
+ span: Span<'source>,
+ term: Box>,
+ query: Query<'source>,
+ },
+
+ ObjectCompr {
+ span: Span<'source>,
+ key: Box>,
+ value: Box>,
+ query: Query<'source>,
+ },
+
+ Call {
+ span: Span<'source>,
+ fcn: Box>,
+ params: Vec>,
+ },
+
+ UnaryExpr {
+ span: Span<'source>,
+ expr: Box>,
+ },
+
+ // ref
+ RefDot {
+ span: Span<'source>,
+ refr: Box>,
+ field: Span<'source>,
+ },
+
+ RefBrack {
+ span: Span<'source>,
+ refr: Box>,
+ index: Box>,
+ },
+
+ // Infix expressions
+ BinExpr {
+ span: Span<'source>,
+ op: BinOp,
+ lhs: Box>,
+ rhs: Box>,
+ },
+ BoolExpr {
+ span: Span<'source>,
+ op: BoolOp,
+ lhs: Box>,
+ rhs: Box>,
+ },
+
+ ArithExpr {
+ span: Span<'source>,
+ op: ArithOp,
+ lhs: Box>,
+ rhs: Box>,
+ },
+
+ AssignExpr {
+ span: Span<'source>,
+ op: AssignOp,
+ lhs: Box>,
+ rhs: Box>,
+ },
+
+ Membership {
+ span: Span<'source>,
+ key: Box>,
+ value: Box