Repository: clouditor/clouditor
Pull request #223 (2021-05-28 15:12:27)

Support for the Cloud Compliance Language (CCL) in Go
Adds initial support for the Cloud Compliance Language (CCL). This will later allow the definition of specifying rules against which our discovered assets will be assessed.

sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=VULNERABILITY)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/security_hotspots?id=clouditor_clouditor&pullRequest=223&resolved=false&types=SECURITY_HOTSPOT) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/security_hotspots?id=clouditor_clouditor&pullRequest=223&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=clouditor_clouditor&pullRequest=223&resolved=false&types=SECURITY_HOTSPOT)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo.png' alt='No Coverage information' width='16' height='16' />](https://sonarcloud.io/component_measures?id=clouditor_clouditor&pullRequest=223&metric=coverage&view=list) No Coverage information  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo.png' alt='No Duplication information' width='16' height='16' />](https://sonarcloud.io/component_measures?id=clouditor_clouditor&pullRequest=223&metric=duplicated_lines_density&view=list) No Duplication information


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=VULNERABILITY)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/security_hotspots?id=clouditor_clouditor&pullRequest=223&resolved=false&types=SECURITY_HOTSPOT) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/security_hotspots?id=clouditor_clouditor&pullRequest=223&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=clouditor_clouditor&pullRequest=223&resolved=false&types=SECURITY_HOTSPOT)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo.png' alt='No Coverage information' width='16' height='16' />](https://sonarcloud.io/component_measures?id=clouditor_clouditor&pullRequest=223&metric=coverage&view=list) No Coverage information  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo.png' alt='No Duplication information' width='16' height='16' />](https://sonarcloud.io/component_measures?id=clouditor_clouditor&pullRequest=223&metric=duplicated_lines_density&view=list) No Duplication information


sonarcloud[bot]: Kudos, SonarCloud Quality Gate passed!

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/bug.png' alt='Bug' width='16' height='16' />](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=BUG) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=BUG) [0 Bugs](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=BUG)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/vulnerability.png' alt='Vulnerability' width='16' height='16' />](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=VULNERABILITY) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=VULNERABILITY) [0 Vulnerabilities](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=VULNERABILITY)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/security_hotspot.png' alt='Security Hotspot' width='16' height='16' />](https://sonarcloud.io/project/security_hotspots?id=clouditor_clouditor&pullRequest=223&resolved=false&types=SECURITY_HOTSPOT) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/security_hotspots?id=clouditor_clouditor&pullRequest=223&resolved=false&types=SECURITY_HOTSPOT) [0 Security Hotspots](https://sonarcloud.io/project/security_hotspots?id=clouditor_clouditor&pullRequest=223&resolved=false&types=SECURITY_HOTSPOT)  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/common/code_smell.png' alt='Code Smell' width='16' height='16' />](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=CODE_SMELL) [<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/RatingBadge/A.png' alt='A' width='16' height='16' />](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=CODE_SMELL) [0 Code Smells](https://sonarcloud.io/project/issues?id=clouditor_clouditor&pullRequest=223&resolved=false&types=CODE_SMELL)

[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/CoverageChart/NoCoverageInfo.png' alt='No Coverage information' width='16' height='16' />](https://sonarcloud.io/component_measures?id=clouditor_clouditor&pullRequest=223&metric=coverage&view=list) No Coverage information  
[<img src='https://sonarsource.github.io/sonarcloud-github-static-resources/v2/checks/Duplications/NoDuplicationInfo.png' alt='No Duplication information' width='16' height='16' />](https://sonarcloud.io/component_measures?id=clouditor_clouditor&pullRequest=223&metric=duplicated_lines_density&view=list) No Duplication information


codecov[bot]: # [Codecov](https://codecov.io/gh/clouditor/clouditor/pull/223?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor) Report
> Merging [#223](https://codecov.io/gh/clouditor/clouditor/pull/223?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor) (aa84bbb) into [main](https://codecov.io/gh/clouditor/clouditor/commit/a5bb68e20fe5fc7396df2ae85c404687f6193be8?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor) (a5bb68e) will **increase** coverage by `4.90%`.
> The diff coverage is `23.53%`.

[![Impacted file tree graph](https://codecov.io/gh/clouditor/clouditor/pull/223/graphs/tree.svg?width=650&height=150&src=pr&token=VC87ODO814&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor)](https://codecov.io/gh/clouditor/clouditor/pull/223?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor)

```diff
@@            Coverage Diff            @@
##            main     #223      +/-   ##
=========================================
+ Coverage   7.12%   12.03%   +4.90%     
=========================================
  Files         31       35       +4     
  Lines       2652     3782    +1130     
=========================================
+ Hits         189      455     +266     
- Misses      2452     3294     +842     
- Partials      11       33      +22     
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `12.03% <23.53%> (+4.90%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/clouditor/clouditor/pull/223?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor) | Coverage Δ | |
|---|---|---|
| [ccl/parser/ccl\_base\_listener.go](https://codecov.io/gh/clouditor/clouditor/pull/223/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor#diff-Y2NsL3BhcnNlci9jY2xfYmFzZV9saXN0ZW5lci5nbw==) | `0.00% <0.00%> (ø)` | |
| [ccl/parser/ccl\_parser.go](https://codecov.io/gh/clouditor/clouditor/pull/223/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor#diff-Y2NsL3BhcnNlci9jY2xfcGFyc2VyLmdv) | `20.85% <20.85%> (ø)` | |
| [ccl/ccl.go](https://codecov.io/gh/clouditor/clouditor/pull/223/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor#diff-Y2NsL2NjbC5nbw==) | `63.23% <63.23%> (ø)` | |
| [ccl/parser/ccl\_lexer.go](https://codecov.io/gh/clouditor/clouditor/pull/223/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor#diff-Y2NsL3BhcnNlci9jY2xfbGV4ZXIuZ28=) | `100.00% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/clouditor/clouditor/pull/223?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/clouditor/clouditor/pull/223?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor). Last update [a5bb68e...aa84bbb](https://codecov.io/gh/clouditor/clouditor/pull/223?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clouditor).


Patch:
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index bdd7fbf5..4654bada 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -35,7 +35,6 @@ jobs:
         with:
           files: coverage.cov
           flags: unittests
-          verbose: true
       - name: "Upload Artifact"
         uses: actions/upload-artifact@v2
         with:
diff --git a/ccl/README.md b/ccl/README.md
new file mode 100644
index 00000000..2543566a
--- /dev/null
+++ b/ccl/README.md
@@ -0,0 +1,26 @@
+# Cloud Compliance Language (CCL)
+
+It is a simple domain specific language to model rules that should apply to discovered resources. It has the following
+syntax:
+```ccl
+<resourceType> has <expression>
+```
+
+The `<resourceType>` refers to an existing resource type in the vocabulary. See package voc.
+The `<expression>` must evaluate to a boolean expression, which decide whether the resource is compliant or not
+
+## Expressions
+
+Several different expression exists.
+
+### Comparison
+
+For example, a simple comparison of two values can be achieved using
+
+```ccl
+<field> <operatorType> <literalValue>
+```
+
+In this case, <field> refers to a field in the resource type, defined the vocabulary. See package voc.
+<operatorType> can either be `==`, `!=`, `<=`, `<`, `>`, `>=`, or the special `contains` keyword.
+<literalValue> refers to a literal in either a string, integer, float or boolean format.
diff --git a/ccl/ccl.go b/ccl/ccl.go
new file mode 100644
index 00000000..e95353d5
--- /dev/null
+++ b/ccl/ccl.go
@@ -0,0 +1,535 @@
+// Copyright 2021 Fraunhofer AISEC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//           $$\                           $$\ $$\   $$\
+//           $$ |                          $$ |\__|  $$ |
+//  $$$$$$$\ $$ | $$$$$$\  $$\   $$\  $$$$$$$ |$$\ $$$$$$\    $$$$$$\   $$$$$$\
+// $$  _____|$$ |$$  __$$\ $$ |  $$ |$$  __$$ |$$ |\_$$  _|  $$  __$$\ $$  __$$\
+// $$ /      $$ |$$ /  $$ |$$ |  $$ |$$ /  $$ |$$ |  $$ |    $$ /  $$ |$$ | \__|
+// $$ |      $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$\ $$ |  $$ |$$ |
+// \$$$$$$\  $$ |\$$$$$   |\$$$$$   |\$$$$$$  |$$ |  \$$$   |\$$$$$   |$$ |
+//  \_______|\__| \______/  \______/  \_______|\__|   \____/  \______/ \__|
+//
+// This file is part of Clouditor Community Edition.
+
+// Package ccl contains the the Cloud Compliance Language (CCL). It is a simple domain specific language to model rules
+// that should apply to discovered resources.
+package ccl
+
+import (
+	"errors"
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/antlr/antlr4/runtime/Go/antlr"
+	"github.com/sirupsen/logrus"
+
+	"clouditor.io/clouditor/ccl/parser"
+)
+
+var log *logrus.Entry
+
+var (
+	ErrUnsupportedContext   = errors.New("unsupported context")
+	ErrFieldNameNotFound    = errors.New("invalid field name")
+	ErrFieldNoMap           = errors.New("field is not a map")
+	ErrFieldNoTime          = errors.New("field has no time value")
+	ErrFieldNoArray         = errors.New("field is not an array")
+	ErrInvalidScope         = errors.New("invalid scope in in-expression")
+	ErrUnexpectedExpression = errors.New("unexpected expression")
+)
+
+func init() {
+	log = logrus.WithField("component", "ccl")
+}
+
+func RunRule(data string, object map[string]interface{}) (bool, error) {
+	log.Debugf("Evaluating '%s'...", data)
+
+	input := antlr.NewInputStream(data)
+
+	return runRule(input, object)
+}
+
+func RunRuleFromFile(file string, object map[string]interface{}) (bool, error) {
+	log.Debugf("Evaluating rule %s...", file)
+
+	input, _ := antlr.NewFileStream(file)
+
+	return runRule(input, object)
+}
+
+func runRule(input antlr.CharStream, object map[string]interface{}) (bool, error) {
+	lexer := parser.NewCCLLexer(input)
+	stream := antlr.NewCommonTokenStream(lexer, 0)
+	p := parser.NewCCLParser(stream)
+	p.AddErrorListener(antlr.NewDiagnosticErrorListener(true))
+	tree := p.Condition()
+
+	return evaluateCondition(tree, object)
+}
+
+func evaluateCondition(c parser.IConditionContext, o map[string]interface{}) (success bool, err error) {
+	if v, ok := c.(*parser.ConditionContext); ok {
+		return evaluateExpression(v.Expression(), o)
+	}
+
+	return false, ErrUnsupportedContext
+}
+
+func evaluateExpression(c parser.IExpressionContext, o map[string]interface{}) (success bool, err error) {
+	if v, ok := c.(*parser.ExpressionContext); ok {
+		if v.SimpleExpression() != nil {
+			return evaluateSimpleExpression(v.SimpleExpression(), o)
+		} else if v.NotExpression() != nil {
+			return evaluateNotExpression(v.NotExpression(), o)
+		} else if v.InExpression() != nil {
+			return evaluateInExpression(v.InExpression(), o)
+		} else {
+			return false, ErrUnexpectedExpression
+		}
+	}
+
+	return false, ErrUnsupportedContext
+}
+
+func evaluateSimpleExpression(c parser.ISimpleExpressionContext, o map[string]interface{}) (success bool, err error) {
+	if v, ok := c.(*parser.SimpleExpressionContext); ok {
+		if v.IsEmptyExpression() != nil {
+			return evaluateIsEmptyExpression(v.IsEmptyExpression(), o)
+		} else if v.WithinExpression() != nil {
+			return evaluateWithinExpression(v.WithinExpression(), o)
+		} else if v.Comparison() != nil {
+			return evaluateComparison(v.Comparison(), o)
+		} else if v.Expression() != nil {
+			return evaluateExpression(v.Expression(), o)
+		}
+	}
+
+	return false, ErrUnsupportedContext
+}
+
+func evaluateNotExpression(c parser.INotExpressionContext, o map[string]interface{}) (success bool, err error) {
+	if v, ok := c.(*parser.NotExpressionContext); ok {
+		// evalute the expression and negate it
+		success, err = evaluateExpression(v.Expression(), o)
+		success = !success
+
+		return
+	}
+
+	return false, ErrUnsupportedContext
+}
+
+func evaluateInExpression(c parser.IInExpressionContext, o map[string]interface{}) (success bool, err error) {
+	if v, ok := c.(*parser.InExpressionContext); ok {
+		var value interface{}
+		var arrayValue []map[string]interface{}
+
+		if value, err = evaluateField(v.Field().GetText(), o); err != nil {
+			return false, fmt.Errorf("could not evaluate in-expression: %w", err)
+		}
+
+		if arrayValue, ok = value.([]map[string]interface{}); !ok {
+			return false, ErrFieldNoArray
+		}
+
+		var scope string
+		if v.Scope().GetText() == "all" {
+			scope = "all"
+		} else if v.Scope().GetText() == "any" {
+			scope = "any"
+		} else {
+			return false, ErrInvalidScope
+		}
+
+		var result bool
+
+		// loop through array
+		for _, item := range arrayValue {
+			// if any matches
+			if success, err = evaluateSimpleExpression(v.SimpleExpression(), item); err != nil {
+				return false, fmt.Errorf("could not evaluate in-expression: %w", err)
+			}
+
+			if success && scope == "any" {
+				return true, nil
+			}
+
+			result = result && success
+		}
+
+		return result, nil
+	}
+
+	return false, ErrUnsupportedContext
+}
+
+func evaluateIsEmptyExpression(c parser.IIsEmptyExpressionContext, o map[string]interface{}) (success bool, err error) {
+	if v, ok := c.(*parser.IsEmptyExpressionContext); ok {
+		// evalute the field
+		value, err := evaluateField(v.Field().GetText(), o)
+		if errors.Is(err, ErrFieldNameNotFound) {
+			// if the field is not found, it is also empty
+			return true, nil
+		} else if err != nil {
+			return false, fmt.Errorf("could not evaluate field: %w", err)
+		}
+
+		// otherwise, check, if the value is an "empty" value
+		return value == "" || value == 0 || value == false, nil
+	}
+
+	return false, ErrUnsupportedContext
+}
+
+// evaluateWithinExpression checks, if a field value is within a specified array
+func evaluateWithinExpression(c parser.IWithinExpressionContext, o map[string]interface{}) (success bool, err error) {
+	if v, ok := c.(*parser.WithinExpressionContext); ok {
+		var (
+			lhs interface{}
+			rhs interface{}
+			op  Operator
+		)
+
+		op = &equalsOperator{}
+
+		identifier := v.Field().(*parser.FieldContext).Identifier().GetText()
+
+		if lhs, err = evaluateField(identifier, o); err != nil {
+			return false, fmt.Errorf("could not evaluate field %s: %w", identifier, err)
+		}
+
+		ctxs := v.AllValue()
+		for _, ctx := range ctxs {
+			if rhs, err = evaluateValue(ctx); err != nil {
+				return false, fmt.Errorf("could not evaluate value: %w", err)
+			}
+
+			if success, err = compare(lhs, rhs, op); err != nil {
+				// directly return error without wrapping, i.e. if the comparison was of a different type
+				return false, err
+			}
+
+			// return early, if a match was found
+			if success {
+				return true, nil
+			}
+		}
+
+		// no match found
+		return false, nil
+	}
+
+	return false, ErrUnsupportedContext
+}
+
+func evaluateComparison(c parser.IComparisonContext, o map[string]interface{}) (success bool, err error) {
+	if v, ok := c.(*parser.ComparisonContext); ok {
+		if v.BinaryComparison() != nil {
+			return evaluteBinaryComparison(v.BinaryComparison(), o)
+		} else if v.TimeComparison() != nil {
+			return evaluateTimeComparison(v.TimeComparison(), o)
+		} else {
+			return false, ErrUnexpectedExpression
+		}
+	}
+
+	return false, ErrUnsupportedContext
+}
+
+func evaluteBinaryComparison(c parser.IBinaryComparisonContext, o map[string]interface{}) (success bool, err error) {
+	if v, ok := c.(*parser.BinaryComparisonContext); ok {
+		// now the fun begins
+		identifier := v.Field().(*parser.FieldContext).Identifier().GetText()
+
+		var (
+			lhs interface{}
+			rhs interface{}
+			op  Operator
+		)
+
+		if lhs, err = evaluateField(identifier, o); err != nil {
+			return false, fmt.Errorf("could not evaluate field %s: %w", identifier, err)
+		}
+
+		if rhs, err = evaluateValue(v.Value()); err != nil {
+			return false, fmt.Errorf("could not evaluate value: %w", err)
+		}
+
+		if op, err = evaluateOperator(v.Operator()); err != nil {
+			return false, fmt.Errorf("could not parse operator: %w", err)
+		}
+
+		return compare(lhs, rhs, op)
+	}
+
+	return false, ErrUnsupportedContext
+}
+
+func evaluateTimeComparison(c parser.ITimeComparisonContext, o map[string]interface{}) (success bool, err error) {
+	if v, ok := c.(*parser.TimeComparisonContext); ok {
+		var (
+			lhs       interface{}
+			timeValue time.Time
+			duration  time.Duration
+		)
+
+		identifier := v.Field().(*parser.FieldContext).Identifier().GetText()
+
+		if lhs, err = evaluateField(identifier, o); err != nil {
+			return false, fmt.Errorf("could not evaluate field %s: %w", identifier, err)
+		}
+
+		if timeValue, ok = lhs.(time.Time); !ok {
+			return false, ErrFieldNoTime
+		}
+
+		if v.Duration() != nil {
+			if duration, err = evaluateDuration(v.Duration()); err != nil {
+				return false, fmt.Errorf("could not compare times: %w", err)
+			}
+		} else if v.NowOperator() != nil {
+			duration = 0
+		}
+
+		// four different operators, but all relative to the current time
+		now := time.Now()
+
+		// younger than X (days|minutes|seconds)
+		if v.TimeOperator().GetText() == "younger" {
+			// substract the duration specified in rhs from now (go back into the past) to get the
+			// beginning of the period we consider 'younger'
+			younger := now.Add(-duration)
+
+			// check, if timeValue is after or equal to the 'younger' date
+			return timeValue.After(younger) || timeValue.Equal(younger), nil
+		}
+
+		// older than X (seconds|days|months)
+		if v.TimeOperator().GetText() == "older" {
+			// basically, the same as younger, just before - not after
+			older := now.Add(-duration)
+
+			return timeValue.Before(older), nil
+		}
+
+		// before X (seconds|days|months)
+		if v.TimeOperator().GetText() == "before" {
+			// add the duration specified in rhs to now (going into the future) to get the
+			// end of the period we consider 'before'
+			before := now.Add(duration)
+
+			// check if timeValue is before or equal to the 'before' date
+			return timeValue.Before(before) || timeValue.Equal(before), nil
+		}
+
+		// after X (seconds|days|months)
+		if v.TimeOperator().GetText() == "after" {
+			// basically, the same as before, just after - not before
+			after := now.Add(duration)
+
+			// check if timeValue is before or equal to the 'before' date
+			return timeValue.After(after), nil
+		}
+	}
+
+	return false, ErrUnsupportedContext
+}
+
+func evaluateDuration(c parser.IDurationContext) (duration time.Duration, err error) {
+	if v, ok := c.(*parser.DurationContext); ok {
+		var value int64
+		if value, err = evaluteIntegerLiteral(v.IntNumber()); err != nil {
+			return 0, fmt.Errorf("invalid value: %w", err)
+		}
+
+		t := v.Unit().GetText()
+		if t == "seconds" {
+			return time.Duration(value) * time.Second, nil
+		} else if t == "days" {
+			return time.Duration(value) * time.Hour * 24, nil
+		} else if t == "months" {
+			return time.Duration(value) * time.Hour * 24 * 30, nil
+		} else {
+			return 0, fmt.Errorf("invalid unit duration: %s", t)
+		}
+	}
+
+	return 0, ErrUnsupportedContext
+}
+
+func evaluateField(field string, o map[string]interface{}) (interface{}, error) {
+	var (
+		idx   int
+		value interface{}
+		ok    bool
+	)
+
+	if idx = strings.Index(field, "."); idx == -1 {
+		// no sub field left, directly access it
+		if value, ok = o[field]; !ok {
+			return nil, ErrFieldNameNotFound
+		}
+
+		return value, nil
+	} else {
+		// check for the first part; if that does not exist, we do not need to continue
+		firstPart := field[:idx]
+		if value, ok = o[firstPart]; !ok {
+			return nil, ErrFieldNameNotFound
+		}
+
+		mapValue, ok := value.(map[string]interface{})
+		if !ok {
+			return nil, ErrFieldNoMap
+		}
+
+		// recursivly check for the second part
+		secondPart := field[idx+1:]
+
+		return evaluateField(secondPart, mapValue)
+	}
+}
+
+func evaluateValue(c parser.IValueContext) (interface{}, error) {
+	if v, ok := c.(*parser.ValueContext); ok {
+		if v.StringLiteral() != nil {
+			return evaluateStringLiteral(v.StringLiteral()), nil
+		} else if v.IntNumber() != nil {
+			return evaluteIntegerLiteral(v.IntNumber())
+		} else if v.FloatNumber() != nil {
+			return evaluateFloatLiteral(v.FloatNumber())
+		} else if v.BooleanLiteral() != nil {
+			return evaluateBoolLiteral(v.BooleanLiteral())
+		}
+
+		return nil, fmt.Errorf("could not evalute literal: %v", v.GetText())
+	}
+
+	return nil, ErrUnsupportedContext
+}
+
+func evaluateStringLiteral(node antlr.TerminalNode) string {
+	s := node.GetText()
+
+	return strings.Trim(s, "\"'")
+}
+
+func evaluteIntegerLiteral(node antlr.TerminalNode) (int64, error) {
+	return strconv.ParseInt(node.GetText(), 10, 64)
+}
+
+func evaluateFloatLiteral(node antlr.TerminalNode) (float64, error) {
+	return strconv.ParseFloat(node.GetText(), 64)
+}
+
+func evaluateBoolLiteral(node antlr.TerminalNode) (bool, error) {
+	return strconv.ParseBool(node.GetText())
+}
+
+func evaluateOperator(c parser.IOperatorContext) (Operator, error) {
+	if v, ok := c.(*parser.OperatorContext); ok {
+		if v.EqualsOperator() != nil {
+			return &equalsOperator{}, nil
+		}
+
+		if v.NotEqualsOperator() != nil {
+			return &notOperator{&equalsOperator{}}, nil
+		}
+
+		if v.LessThanOperator() != nil {
+			return &lessOperator{}, nil
+		}
+
+		if v.LessOrEqualsThanOperator() != nil {
+			return &notOperator{greaterOperator{}}, nil
+		}
+
+		if v.MoreThanOperator() != nil {
+			return &greaterOperator{}, nil
+		}
+
+		if v.MoreOrEqualsThanOperator() != nil {
+			return &notOperator{lessOperator{}}, nil
+		}
+
+		if v.ContainsOperator() != nil {
+			return &containsOperator{}, nil
+		}
+	}
+
+	return nil, ErrUnsupportedContext
+}
+
+func compare(lhs interface{}, rhs interface{}, op Operator) (success bool, err error) {
+	switch v := lhs.(type) {
+	case string:
+		return compareString(v, rhs, op)
+	case int:
+		return compareInt(int64(v), rhs, 64, op)
+	case int16:
+		return compareInt(int64(v), rhs, 64, op)
+	case int32:
+		return compareInt(int64(v), rhs, 64, op)
+	case int64:
+		return compareInt(int64(v), rhs, 64, op)
+	case float32:
+		return compareFloat(float64(v), rhs, 64, op)
+	case float64:
+		return compareFloat(float64(v), rhs, 64, op)
+	case bool:
+		return compareBool(v, rhs, op)
+	}
+
+	return false, fmt.Errorf("could not compare %+v and %+v", lhs, rhs)
+}
+
+func compareString(v string, rhs interface{}, op Operator) (success bool, err error) {
+	var s = fmt.Sprintf("%v", rhs)
+
+	return op.CompareString(v, s)
+}
+
+func compareInt(v int64, rhs interface{}, bitSize int, op Operator) (success bool, err error) {
+	var i int64
+	// try to convert rhs to integer
+	if i, err = strconv.ParseInt(fmt.Sprintf("%v", rhs), 10, bitSize); err != nil {
+		return false, fmt.Errorf("could not compare %+v and %+v: %w", v, rhs, err)
+	}
+
+	return op.CompareInt(v, i)
+}
+
+func compareFloat(v float64, rhs interface{}, bitSize int, op Operator) (success bool, err error) {
+	var f float64
+	// try to convert rhs to integer
+	if f, err = strconv.ParseFloat(fmt.Sprintf("%v", rhs), bitSize); err != nil {
+		return false, fmt.Errorf("could not compare %+v and %+v: %w", v, rhs, err)
+	}
+
+	return op.CompareFloat(v, f)
+}
+
+func compareBool(v bool, rhs interface{}, op Operator) (success bool, err error) {
+	var b bool
+	// try to convert rhs to integer
+	if b, err = strconv.ParseBool(fmt.Sprintf("%v", rhs)); err != nil {
+		return false, fmt.Errorf("could not compare %+v and %+v: %w", v, rhs, err)
+	}
+
+	return op.CompareBool(v, b)
+}
diff --git a/ccl/operators.go b/ccl/operators.go
new file mode 100644
index 00000000..1e630e68
--- /dev/null
+++ b/ccl/operators.go
@@ -0,0 +1,115 @@
+// Copyright 2021 Fraunhofer AISEC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//           $$\                           $$\ $$\   $$\
+//           $$ |                          $$ |\__|  $$ |
+//  $$$$$$$\ $$ | $$$$$$\  $$\   $$\  $$$$$$$ |$$\ $$$$$$\    $$$$$$\   $$$$$$\
+// $$  _____|$$ |$$  __$$\ $$ |  $$ |$$  __$$ |$$ |\_$$  _|  $$  __$$\ $$  __$$\
+// $$ /      $$ |$$ /  $$ |$$ |  $$ |$$ /  $$ |$$ |  $$ |    $$ /  $$ |$$ | \__|
+// $$ |      $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$\ $$ |  $$ |$$ |
+// \$$$$$$\  $$ |\$$$$$   |\$$$$$   |\$$$$$$  |$$ |  \$$$   |\$$$$$   |$$ |
+//  \_______|\__| \______/  \______/  \_______|\__|   \____/  \______/ \__|
+//
+// This file is part of Clouditor Community Edition.
+
+package ccl
+
+import (
+	"errors"
+	"strings"
+)
+
+type Operator interface {
+	CompareString(lhs string, rhs string) (bool, error)
+	CompareInt(lhs int64, rhs int64) (bool, error)
+	CompareFloat(lhs float64, rhs float64) (bool, error)
+	CompareBool(lhs bool, rhs bool) (bool, error)
+}
+
+var (
+	ErrOperationNotSupported = errors.New("the specified operation is not supported between the two value types")
+)
+
+type equalsOperator struct{}
+
+func (e equalsOperator) CompareString(lhs string, rhs string) (bool, error)  { return lhs == rhs, nil }
+func (e equalsOperator) CompareInt(lhs int64, rhs int64) (bool, error)       { return lhs == rhs, nil }
+func (e equalsOperator) CompareFloat(lhs float64, rhs float64) (bool, error) { return lhs == rhs, nil }
+func (e equalsOperator) CompareBool(lhs bool, rhs bool) (bool, error)        { return lhs == rhs, nil }
+
+type lessOperator struct{}
+
+func (e lessOperator) CompareString(lhs string, rhs string) (bool, error)  { return lhs < rhs, nil }
+func (e lessOperator) CompareInt(lhs int64, rhs int64) (bool, error)       { return lhs < rhs, nil }
+func (e lessOperator) CompareFloat(lhs float64, rhs float64) (bool, error) { return lhs < rhs, nil }
+func (e lessOperator) CompareBool(lhs bool, rhs bool) (bool, error) {
+	return false, ErrOperationNotSupported
+}
+
+type greaterOperator struct{}
+
+func (e greaterOperator) CompareString(lhs string, rhs string) (bool, error)  { return lhs > rhs, nil }
+func (e greaterOperator) CompareInt(lhs int64, rhs int64) (bool, error)       { return lhs > rhs, nil }
+func (e greaterOperator) CompareFloat(lhs float64, rhs float64) (bool, error) { return lhs > rhs, nil }
+func (e greaterOperator) CompareBool(lhs bool, rhs bool) (bool, error) {
+	return false, ErrOperationNotSupported
+}
+
+type containsOperator struct{}
+
+func (e containsOperator) CompareString(lhs string, rhs string) (bool, error) {
+	return strings.Contains(lhs, rhs), nil
+}
+func (e containsOperator) CompareInt(lhs int64, rhs int64) (bool, error) {
+	return false, ErrOperationNotSupported
+}
+func (e containsOperator) CompareFloat(lhs float64, rhs float64) (bool, error) {
+	return false, ErrOperationNotSupported
+}
+func (e containsOperator) CompareBool(lhs bool, rhs bool) (bool, error) {
+	return false, ErrOperationNotSupported
+}
+
+type notOperator struct {
+	op Operator
+}
+
+func (e notOperator) CompareString(lhs string, rhs string) (b bool, err error) {
+	if b, err = e.op.CompareString(lhs, rhs); err != nil {
+		return false, err
+	}
+
+	return !b, nil
+}
+func (e notOperator) CompareInt(lhs int64, rhs int64) (b bool, err error) {
+	if b, err = e.op.CompareInt(lhs, rhs); err != nil {
+		return false, err
+	}
+
+	return !b, nil
+}
+func (e notOperator) CompareFloat(lhs float64, rhs float64) (b bool, err error) {
+	if b, err = e.op.CompareFloat(lhs, rhs); err != nil {
+		return false, err
+	}
+
+	return !b, nil
+}
+func (e notOperator) CompareBool(lhs bool, rhs bool) (b bool, err error) {
+	if b, err = e.op.CompareBool(lhs, rhs); err != nil {
+		return false, err
+	}
+
+	return !b, nil
+}
diff --git a/ccl/parser/.gitignore b/ccl/parser/.gitignore
new file mode 100644
index 00000000..641bab74
--- /dev/null
+++ b/ccl/parser/.gitignore
@@ -0,0 +1,3 @@
+.antlr
+*.tokens
+*.interp
\ No newline at end of file
diff --git a/ccl/parser/CCL.g4 b/ccl/parser/CCL.g4
new file mode 100644
index 00000000..75719e2a
--- /dev/null
+++ b/ccl/parser/CCL.g4
@@ -0,0 +1,80 @@
+grammar CCL;
+
+condition: assetType 'has' expression EOF;
+
+assetType: simpleAssetType /*| filteredAssetType*/;
+
+simpleAssetType: field;
+
+//filteredAssetType: field 'with' expression;
+
+field: Identifier;
+expression: simpleExpression | notExpression | inExpression;
+
+simpleExpression:
+	isEmptyExpression
+	| withinExpression
+	| comparison
+	| '(' expression ')';
+
+notExpression: 'not' expression;
+
+isEmptyExpression: 'empty' field;
+
+comparison: binaryComparison | timeComparison;
+
+binaryComparison: field operator value;
+
+timeComparison: field timeOperator (duration | nowOperator);
+timeOperator:
+	BeforeOperator
+	| AfterOperator
+	| YoungerOperator
+	| OlderOperator;
+nowOperator: 'now';
+duration: IntNumber unit;
+unit: 'seconds' | 'days' | 'months';
+
+inExpression: simpleExpression 'in' scope field;
+
+scope: 'any' | 'all';
+
+withinExpression: field 'within' (value ','?)+;
+
+value: StringLiteral | BooleanLiteral | IntNumber | FloatNumber;
+
+operator:
+	EqualsOperator
+	| NotEqualsOperator
+	| LessOrEqualsThanOperator
+	| LessThanOperator
+	| MoreThanOperator
+	| MoreOrEqualsThanOperator
+	| ContainsOperator;
+
+EqualsOperator: '==';
+NotEqualsOperator: '!=';
+LessOrEqualsThanOperator: '<=';
+LessThanOperator: '<';
+MoreThanOperator: '>';
+MoreOrEqualsThanOperator: '>=';
+ContainsOperator: 'contains';
+
+BeforeOperator: 'before';
+AfterOperator: 'after';
+YoungerOperator: 'younger';
+OlderOperator: 'older';
+
+BooleanLiteral: True | False;
+
+True: 'true';
+False: 'false';
+
+Identifier: [a-zA-Z][a-zA-Z0-9.]*;
+
+IntNumber: [0-9]+;
+FloatNumber: [0-9.]+;
+
+StringLiteral: '"' ~('"')* '"';
+
+Whitespace: [ \t\u000C\r\n]+ -> skip;
diff --git a/ccl/parser/ccl_lexer.go b/ccl/parser/ccl_lexer.go
new file mode 100644
index 00000000..94c2377f
--- /dev/null
+++ b/ccl/parser/ccl_lexer.go
@@ -0,0 +1,222 @@
+// Code generated from CCL.g4 by ANTLR 4.9.2. DO NOT EDIT.
+
+package parser
+
+import (
+	"fmt"
+	"unicode"
+
+	"github.com/antlr/antlr4/runtime/Go/antlr"
+)
+
+// Suppress unused import error
+var _ = fmt.Printf
+var _ = unicode.IsLetter
+
+var serializedLexerAtn = []uint16{
+	3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 2, 35, 231,
+	8, 1, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7,
+	9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12,
+	4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4,
+	18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23,
+	9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9,
+	28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33,
+	4, 34, 9, 34, 3, 2, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 4, 3, 4, 3, 5, 3,
+	5, 3, 5, 3, 5, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 6, 3, 7, 3, 7, 3, 7, 3,
+	7, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 8, 3, 9, 3, 9, 3, 9, 3,
+	9, 3, 9, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 11, 3, 11,
+	3, 11, 3, 12, 3, 12, 3, 12, 3, 12, 3, 13, 3, 13, 3, 13, 3, 13, 3, 14, 3,
+	14, 3, 14, 3, 14, 3, 14, 3, 14, 3, 14, 3, 15, 3, 15, 3, 16, 3, 16, 3, 16,
+	3, 17, 3, 17, 3, 17, 3, 18, 3, 18, 3, 18, 3, 19, 3, 19, 3, 20, 3, 20, 3,
+	21, 3, 21, 3, 21, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22, 3, 22,
+	3, 22, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 24, 3, 24, 3,
+	24, 3, 24, 3, 24, 3, 24, 3, 25, 3, 25, 3, 25, 3, 25, 3, 25, 3, 25, 3, 25,
+	3, 25, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 26, 3, 27, 3, 27, 5, 27, 186,
+	10, 27, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 3, 29, 3, 29, 3, 29, 3, 29,
+	3, 29, 3, 29, 3, 30, 3, 30, 7, 30, 201, 10, 30, 12, 30, 14, 30, 204, 11,
+	30, 3, 31, 6, 31, 207, 10, 31, 13, 31, 14, 31, 208, 3, 32, 6, 32, 212,
+	10, 32, 13, 32, 14, 32, 213, 3, 33, 3, 33, 7, 33, 218, 10, 33, 12, 33,
+	14, 33, 221, 11, 33, 3, 33, 3, 33, 3, 34, 6, 34, 226, 10, 34, 13, 34, 14,
+	34, 227, 3, 34, 3, 34, 2, 2, 35, 3, 3, 5, 4, 7, 5, 9, 6, 11, 7, 13, 8,
+	15, 9, 17, 10, 19, 11, 21, 12, 23, 13, 25, 14, 27, 15, 29, 16, 31, 17,
+	33, 18, 35, 19, 37, 20, 39, 21, 41, 22, 43, 23, 45, 24, 47, 25, 49, 26,
+	51, 27, 53, 28, 55, 29, 57, 30, 59, 31, 61, 32, 63, 33, 65, 34, 67, 35,
+	3, 2, 8, 4, 2, 67, 92, 99, 124, 6, 2, 48, 48, 50, 59, 67, 92, 99, 124,
+	3, 2, 50, 59, 4, 2, 48, 48, 50, 59, 3, 2, 36, 36, 5, 2, 11, 12, 14, 15,
+	34, 34, 2, 236, 2, 3, 3, 2, 2, 2, 2, 5, 3, 2, 2, 2, 2, 7, 3, 2, 2, 2, 2,
+	9, 3, 2, 2, 2, 2, 11, 3, 2, 2, 2, 2, 13, 3, 2, 2, 2, 2, 15, 3, 2, 2, 2,
+	2, 17, 3, 2, 2, 2, 2, 19, 3, 2, 2, 2, 2, 21, 3, 2, 2, 2, 2, 23, 3, 2, 2,
+	2, 2, 25, 3, 2, 2, 2, 2, 27, 3, 2, 2, 2, 2, 29, 3, 2, 2, 2, 2, 31, 3, 2,
+	2, 2, 2, 33, 3, 2, 2, 2, 2, 35, 3, 2, 2, 2, 2, 37, 3, 2, 2, 2, 2, 39, 3,
+	2, 2, 2, 2, 41, 3, 2, 2, 2, 2, 43, 3, 2, 2, 2, 2, 45, 3, 2, 2, 2, 2, 47,
+	3, 2, 2, 2, 2, 49, 3, 2, 2, 2, 2, 51, 3, 2, 2, 2, 2, 53, 3, 2, 2, 2, 2,
+	55, 3, 2, 2, 2, 2, 57, 3, 2, 2, 2, 2, 59, 3, 2, 2, 2, 2, 61, 3, 2, 2, 2,
+	2, 63, 3, 2, 2, 2, 2, 65, 3, 2, 2, 2, 2, 67, 3, 2, 2, 2, 3, 69, 3, 2, 2,
+	2, 5, 73, 3, 2, 2, 2, 7, 75, 3, 2, 2, 2, 9, 77, 3, 2, 2, 2, 11, 81, 3,
+	2, 2, 2, 13, 87, 3, 2, 2, 2, 15, 91, 3, 2, 2, 2, 17, 99, 3, 2, 2, 2, 19,
+	104, 3, 2, 2, 2, 21, 111, 3, 2, 2, 2, 23, 114, 3, 2, 2, 2, 25, 118, 3,
+	2, 2, 2, 27, 122, 3, 2, 2, 2, 29, 129, 3, 2, 2, 2, 31, 131, 3, 2, 2, 2,
+	33, 134, 3, 2, 2, 2, 35, 137, 3, 2, 2, 2, 37, 140, 3, 2, 2, 2, 39, 142,
+	3, 2, 2, 2, 41, 144, 3, 2, 2, 2, 43, 147, 3, 2, 2, 2, 45, 156, 3, 2, 2,
+	2, 47, 163, 3, 2, 2, 2, 49, 169, 3, 2, 2, 2, 51, 177, 3, 2, 2, 2, 53, 185,
+	3, 2, 2, 2, 55, 187, 3, 2, 2, 2, 57, 192, 3, 2, 2, 2, 59, 198, 3, 2, 2,
+	2, 61, 206, 3, 2, 2, 2, 63, 211, 3, 2, 2, 2, 65, 215, 3, 2, 2, 2, 67, 225,
+	3, 2, 2, 2, 69, 70, 7, 106, 2, 2, 70, 71, 7, 99, 2, 2, 71, 72, 7, 117,
+	2, 2, 72, 4, 3, 2, 2, 2, 73, 74, 7, 42, 2, 2, 74, 6, 3, 2, 2, 2, 75, 76,
+	7, 43, 2, 2, 76, 8, 3, 2, 2, 2, 77, 78, 7, 112, 2, 2, 78, 79, 7, 113, 2,
+	2, 79, 80, 7, 118, 2, 2, 80, 10, 3, 2, 2, 2, 81, 82, 7, 103, 2, 2, 82,
+	83, 7, 111, 2, 2, 83, 84, 7, 114, 2, 2, 84, 85, 7, 118, 2, 2, 85, 86, 7,
+	123, 2, 2, 86, 12, 3, 2, 2, 2, 87, 88, 7, 112, 2, 2, 88, 89, 7, 113, 2,
+	2, 89, 90, 7, 121, 2, 2, 90, 14, 3, 2, 2, 2, 91, 92, 7, 117, 2, 2, 92,
+	93, 7, 103, 2, 2, 93, 94, 7, 101, 2, 2, 94, 95, 7, 113, 2, 2, 95, 96, 7,
+	112, 2, 2, 96, 97, 7, 102, 2, 2, 97, 98, 7, 117, 2, 2, 98, 16, 3, 2, 2,
+	2, 99, 100, 7, 102, 2, 2, 100, 101, 7, 99, 2, 2, 101, 102, 7, 123, 2, 2,
+	102, 103, 7, 117, 2, 2, 103, 18, 3, 2, 2, 2, 104, 105, 7, 111, 2, 2, 105,
+	106, 7, 113, 2, 2, 106, 107, 7, 112, 2, 2, 107, 108, 7, 118, 2, 2, 108,
+	109, 7, 106, 2, 2, 109, 110, 7, 117, 2, 2, 110, 20, 3, 2, 2, 2, 111, 112,
+	7, 107, 2, 2, 112, 113, 7, 112, 2, 2, 113, 22, 3, 2, 2, 2, 114, 115, 7,
+	99, 2, 2, 115, 116, 7, 112, 2, 2, 116, 117, 7, 123, 2, 2, 117, 24, 3, 2,
+	2, 2, 118, 119, 7, 99, 2, 2, 119, 120, 7, 110, 2, 2, 120, 121, 7, 110,
+	2, 2, 121, 26, 3, 2, 2, 2, 122, 123, 7, 121, 2, 2, 123, 124, 7, 107, 2,
+	2, 124, 125, 7, 118, 2, 2, 125, 126, 7, 106, 2, 2, 126, 127, 7, 107, 2,
+	2, 127, 128, 7, 112, 2, 2, 128, 28, 3, 2, 2, 2, 129, 130, 7, 46, 2, 2,
+	130, 30, 3, 2, 2, 2, 131, 132, 7, 63, 2, 2, 132, 133, 7, 63, 2, 2, 133,
+	32, 3, 2, 2, 2, 134, 135, 7, 35, 2, 2, 135, 136, 7, 63, 2, 2, 136, 34,
+	3, 2, 2, 2, 137, 138, 7, 62, 2, 2, 138, 139, 7, 63, 2, 2, 139, 36, 3, 2,
+	2, 2, 140, 141, 7, 62, 2, 2, 141, 38, 3, 2, 2, 2, 142, 143, 7, 64, 2, 2,
+	143, 40, 3, 2, 2, 2, 144, 145, 7, 64, 2, 2, 145, 146, 7, 63, 2, 2, 146,
+	42, 3, 2, 2, 2, 147, 148, 7, 101, 2, 2, 148, 149, 7, 113, 2, 2, 149, 150,
+	7, 112, 2, 2, 150, 151, 7, 118, 2, 2, 151, 152, 7, 99, 2, 2, 152, 153,
+	7, 107, 2, 2, 153, 154, 7, 112, 2, 2, 154, 155, 7, 117, 2, 2, 155, 44,
+	3, 2, 2, 2, 156, 157, 7, 100, 2, 2, 157, 158, 7, 103, 2, 2, 158, 159, 7,
+	104, 2, 2, 159, 160, 7, 113, 2, 2, 160, 161, 7, 116, 2, 2, 161, 162, 7,
+	103, 2, 2, 162, 46, 3, 2, 2, 2, 163, 164, 7, 99, 2, 2, 164, 165, 7, 104,
+	2, 2, 165, 166, 7, 118, 2, 2, 166, 167, 7, 103, 2, 2, 167, 168, 7, 116,
+	2, 2, 168, 48, 3, 2, 2, 2, 169, 170, 7, 123, 2, 2, 170, 171, 7, 113, 2,
+	2, 171, 172, 7, 119, 2, 2, 172, 173, 7, 112, 2, 2, 173, 174, 7, 105, 2,
+	2, 174, 175, 7, 103, 2, 2, 175, 176, 7, 116, 2, 2, 176, 50, 3, 2, 2, 2,
+	177, 178, 7, 113, 2, 2, 178, 179, 7, 110, 2, 2, 179, 180, 7, 102, 2, 2,
+	180, 181, 7, 103, 2, 2, 181, 182, 7, 116, 2, 2, 182, 52, 3, 2, 2, 2, 183,
+	186, 5, 55, 28, 2, 184, 186, 5, 57, 29, 2, 185, 183, 3, 2, 2, 2, 185, 184,
+	3, 2, 2, 2, 186, 54, 3, 2, 2, 2, 187, 188, 7, 118, 2, 2, 188, 189, 7, 116,
+	2, 2, 189, 190, 7, 119, 2, 2, 190, 191, 7, 103, 2, 2, 191, 56, 3, 2, 2,
+	2, 192, 193, 7, 104, 2, 2, 193, 194, 7, 99, 2, 2, 194, 195, 7, 110, 2,
+	2, 195, 196, 7, 117, 2, 2, 196, 197, 7, 103, 2, 2, 197, 58, 3, 2, 2, 2,
+	198, 202, 9, 2, 2, 2, 199, 201, 9, 3, 2, 2, 200, 199, 3, 2, 2, 2, 201,
+	204, 3, 2, 2, 2, 202, 200, 3, 2, 2, 2, 202, 203, 3, 2, 2, 2, 203, 60, 3,
+	2, 2, 2, 204, 202, 3, 2, 2, 2, 205, 207, 9, 4, 2, 2, 206, 205, 3, 2, 2,
+	2, 207, 208, 3, 2, 2, 2, 208, 206, 3, 2, 2, 2, 208, 209, 3, 2, 2, 2, 209,
+	62, 3, 2, 2, 2, 210, 212, 9, 5, 2, 2, 211, 210, 3, 2, 2, 2, 212, 213, 3,
+	2, 2, 2, 213, 211, 3, 2, 2, 2, 213, 214, 3, 2, 2, 2, 214, 64, 3, 2, 2,
+	2, 215, 219, 7, 36, 2, 2, 216, 218, 10, 6, 2, 2, 217, 216, 3, 2, 2, 2,
+	218, 221, 3, 2, 2, 2, 219, 217, 3, 2, 2, 2, 219, 220, 3, 2, 2, 2, 220,
+	222, 3, 2, 2, 2, 221, 219, 3, 2, 2, 2, 222, 223, 7, 36, 2, 2, 223, 66,
+	3, 2, 2, 2, 224, 226, 9, 7, 2, 2, 225, 224, 3, 2, 2, 2, 226, 227, 3, 2,
+	2, 2, 227, 225, 3, 2, 2, 2, 227, 228, 3, 2, 2, 2, 228, 229, 3, 2, 2, 2,
+	229, 230, 8, 34, 2, 2, 230, 68, 3, 2, 2, 2, 9, 2, 185, 202, 208, 213, 219,
+	227, 3, 8, 2, 2,
+}
+
+var lexerChannelNames = []string{
+	"DEFAULT_TOKEN_CHANNEL", "HIDDEN",
+}
+
+var lexerModeNames = []string{
+	"DEFAULT_MODE",
+}
+
+var lexerLiteralNames = []string{
+	"", "'has'", "'('", "')'", "'not'", "'empty'", "'now'", "'seconds'", "'days'",
+	"'months'", "'in'", "'any'", "'all'", "'within'", "','", "'=='", "'!='",
+	"'<='", "'<'", "'>'", "'>='", "'contains'", "'before'", "'after'", "'younger'",
+	"'older'", "", "'true'", "'false'",
+}
+
+var lexerSymbolicNames = []string{
+	"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "EqualsOperator",
+	"NotEqualsOperator", "LessOrEqualsThanOperator", "LessThanOperator", "MoreThanOperator",
+	"MoreOrEqualsThanOperator", "ContainsOperator", "BeforeOperator", "AfterOperator",
+	"YoungerOperator", "OlderOperator", "BooleanLiteral", "True", "False",
+	"Identifier", "IntNumber", "FloatNumber", "StringLiteral", "Whitespace",
+}
+
+var lexerRuleNames = []string{
+	"T__0", "T__1", "T__2", "T__3", "T__4", "T__5", "T__6", "T__7", "T__8",
+	"T__9", "T__10", "T__11", "T__12", "T__13", "EqualsOperator", "NotEqualsOperator",
+	"LessOrEqualsThanOperator", "LessThanOperator", "MoreThanOperator", "MoreOrEqualsThanOperator",
+	"ContainsOperator", "BeforeOperator", "AfterOperator", "YoungerOperator",
+	"OlderOperator", "BooleanLiteral", "True", "False", "Identifier", "IntNumber",
+	"FloatNumber", "StringLiteral", "Whitespace",
+}
+
+type CCLLexer struct {
+	*antlr.BaseLexer
+	channelNames []string
+	modeNames    []string
+	// TODO: EOF string
+}
+
+// NewCCLLexer produces a new lexer instance for the optional input antlr.CharStream.
+//
+// The *CCLLexer instance produced may be reused by calling the SetInputStream method.
+// The initial lexer configuration is expensive to construct, and the object is not thread-safe;
+// however, if used within a Golang sync.Pool, the construction cost amortizes well and the
+// objects can be used in a thread-safe manner.
+func NewCCLLexer(input antlr.CharStream) *CCLLexer {
+	l := new(CCLLexer)
+	lexerDeserializer := antlr.NewATNDeserializer(nil)
+	lexerAtn := lexerDeserializer.DeserializeFromUInt16(serializedLexerAtn)
+	lexerDecisionToDFA := make([]*antlr.DFA, len(lexerAtn.DecisionToState))
+	for index, ds := range lexerAtn.DecisionToState {
+		lexerDecisionToDFA[index] = antlr.NewDFA(ds, index)
+	}
+	l.BaseLexer = antlr.NewBaseLexer(input)
+	l.Interpreter = antlr.NewLexerATNSimulator(l, lexerAtn, lexerDecisionToDFA, antlr.NewPredictionContextCache())
+
+	l.channelNames = lexerChannelNames
+	l.modeNames = lexerModeNames
+	l.RuleNames = lexerRuleNames
+	l.LiteralNames = lexerLiteralNames
+	l.SymbolicNames = lexerSymbolicNames
+	l.GrammarFileName = "CCL.g4"
+	// TODO: l.EOF = antlr.TokenEOF
+
+	return l
+}
+
+// CCLLexer tokens.
+const (
+	CCLLexerT__0                     = 1
+	CCLLexerT__1                     = 2
+	CCLLexerT__2                     = 3
+	CCLLexerT__3                     = 4
+	CCLLexerT__4                     = 5
+	CCLLexerT__5                     = 6
+	CCLLexerT__6                     = 7
+	CCLLexerT__7                     = 8
+	CCLLexerT__8                     = 9
+	CCLLexerT__9                     = 10
+	CCLLexerT__10                    = 11
+	CCLLexerT__11                    = 12
+	CCLLexerT__12                    = 13
+	CCLLexerT__13                    = 14
+	CCLLexerEqualsOperator           = 15
+	CCLLexerNotEqualsOperator        = 16
+	CCLLexerLessOrEqualsThanOperator = 17
+	CCLLexerLessThanOperator         = 18
+	CCLLexerMoreThanOperator         = 19
+	CCLLexerMoreOrEqualsThanOperator = 20
+	CCLLexerContainsOperator         = 21
+	CCLLexerBeforeOperator           = 22
+	CCLLexerAfterOperator            = 23
+	CCLLexerYoungerOperator          = 24
+	CCLLexerOlderOperator            = 25
+	CCLLexerBooleanLiteral           = 26
+	CCLLexerTrue                     = 27
+	CCLLexerFalse                    = 28
+	CCLLexerIdentifier               = 29
+	CCLLexerIntNumber                = 30
+	CCLLexerFloatNumber              = 31
+	CCLLexerStringLiteral            = 32
+	CCLLexerWhitespace               = 33
+)
diff --git a/ccl/parser/ccl_parser.go b/ccl/parser/ccl_parser.go
new file mode 100644
index 00000000..0f3d853c
--- /dev/null
+++ b/ccl/parser/ccl_parser.go
@@ -0,0 +1,2242 @@
+// Code generated from CCL.g4 by ANTLR 4.9.2. DO NOT EDIT.
+
+package parser // CCL
+
+import (
+	"fmt"
+	"reflect"
+	"strconv"
+
+	"github.com/antlr/antlr4/runtime/Go/antlr"
+)
+
+// Suppress unused import errors
+var _ = fmt.Printf
+var _ = reflect.Copy
+var _ = strconv.Itoa
+
+var parserATN = []uint16{
+	3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 3, 35, 118,
+	4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7,
+	4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13,
+	9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9,
+	18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 3, 2, 3, 2, 3, 2, 3, 2, 3,
+	2, 3, 3, 3, 3, 3, 4, 3, 4, 3, 5, 3, 5, 3, 6, 3, 6, 3, 6, 5, 6, 57, 10,
+	6, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 3, 7, 5, 7, 66, 10, 7, 3, 8, 3,
+	8, 3, 8, 3, 9, 3, 9, 3, 9, 3, 10, 3, 10, 5, 10, 76, 10, 10, 3, 11, 3, 11,
+	3, 11, 3, 11, 3, 12, 3, 12, 3, 12, 3, 12, 5, 12, 86, 10, 12, 3, 13, 3,
+	13, 3, 14, 3, 14, 3, 15, 3, 15, 3, 15, 3, 16, 3, 16, 3, 17, 3, 17, 3, 17,
+	3, 17, 3, 17, 3, 18, 3, 18, 3, 19, 3, 19, 3, 19, 3, 19, 5, 19, 108, 10,
+	19, 6, 19, 110, 10, 19, 13, 19, 14, 19, 111, 3, 20, 3, 20, 3, 21, 3, 21,
+	3, 21, 2, 2, 22, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30,
+	32, 34, 36, 38, 40, 2, 7, 3, 2, 24, 27, 3, 2, 9, 11, 3, 2, 13, 14, 4, 2,
+	28, 28, 32, 34, 3, 2, 17, 23, 2, 106, 2, 42, 3, 2, 2, 2, 4, 47, 3, 2, 2,
+	2, 6, 49, 3, 2, 2, 2, 8, 51, 3, 2, 2, 2, 10, 56, 3, 2, 2, 2, 12, 65, 3,
+	2, 2, 2, 14, 67, 3, 2, 2, 2, 16, 70, 3, 2, 2, 2, 18, 75, 3, 2, 2, 2, 20,
+	77, 3, 2, 2, 2, 22, 81, 3, 2, 2, 2, 24, 87, 3, 2, 2, 2, 26, 89, 3, 2, 2,
+	2, 28, 91, 3, 2, 2, 2, 30, 94, 3, 2, 2, 2, 32, 96, 3, 2, 2, 2, 34, 101,
+	3, 2, 2, 2, 36, 103, 3, 2, 2, 2, 38, 113, 3, 2, 2, 2, 40, 115, 3, 2, 2,
+	2, 42, 43, 5, 4, 3, 2, 43, 44, 7, 3, 2, 2, 44, 45, 5, 10, 6, 2, 45, 46,
+	7, 2, 2, 3, 46, 3, 3, 2, 2, 2, 47, 48, 5, 6, 4, 2, 48, 5, 3, 2, 2, 2, 49,
+	50, 5, 8, 5, 2, 50, 7, 3, 2, 2, 2, 51, 52, 7, 31, 2, 2, 52, 9, 3, 2, 2,
+	2, 53, 57, 5, 12, 7, 2, 54, 57, 5, 14, 8, 2, 55, 57, 5, 32, 17, 2, 56,
+	53, 3, 2, 2, 2, 56, 54, 3, 2, 2, 2, 56, 55, 3, 2, 2, 2, 57, 11, 3, 2, 2,
+	2, 58, 66, 5, 16, 9, 2, 59, 66, 5, 36, 19, 2, 60, 66, 5, 18, 10, 2, 61,
+	62, 7, 4, 2, 2, 62, 63, 5, 10, 6, 2, 63, 64, 7, 5, 2, 2, 64, 66, 3, 2,
+	2, 2, 65, 58, 3, 2, 2, 2, 65, 59, 3, 2, 2, 2, 65, 60, 3, 2, 2, 2, 65, 61,
+	3, 2, 2, 2, 66, 13, 3, 2, 2, 2, 67, 68, 7, 6, 2, 2, 68, 69, 5, 10, 6, 2,
+	69, 15, 3, 2, 2, 2, 70, 71, 7, 7, 2, 2, 71, 72, 5, 8, 5, 2, 72, 17, 3,
+	2, 2, 2, 73, 76, 5, 20, 11, 2, 74, 76, 5, 22, 12, 2, 75, 73, 3, 2, 2, 2,
+	75, 74, 3, 2, 2, 2, 76, 19, 3, 2, 2, 2, 77, 78, 5, 8, 5, 2, 78, 79, 5,
+	40, 21, 2, 79, 80, 5, 38, 20, 2, 80, 21, 3, 2, 2, 2, 81, 82, 5, 8, 5, 2,
+	82, 85, 5, 24, 13, 2, 83, 86, 5, 28, 15, 2, 84, 86, 5, 26, 14, 2, 85, 83,
+	3, 2, 2, 2, 85, 84, 3, 2, 2, 2, 86, 23, 3, 2, 2, 2, 87, 88, 9, 2, 2, 2,
+	88, 25, 3, 2, 2, 2, 89, 90, 7, 8, 2, 2, 90, 27, 3, 2, 2, 2, 91, 92, 7,
+	32, 2, 2, 92, 93, 5, 30, 16, 2, 93, 29, 3, 2, 2, 2, 94, 95, 9, 3, 2, 2,
+	95, 31, 3, 2, 2, 2, 96, 97, 5, 12, 7, 2, 97, 98, 7, 12, 2, 2, 98, 99, 5,
+	34, 18, 2, 99, 100, 5, 8, 5, 2, 100, 33, 3, 2, 2, 2, 101, 102, 9, 4, 2,
+	2, 102, 35, 3, 2, 2, 2, 103, 104, 5, 8, 5, 2, 104, 109, 7, 15, 2, 2, 105,
+	107, 5, 38, 20, 2, 106, 108, 7, 16, 2, 2, 107, 106, 3, 2, 2, 2, 107, 108,
+	3, 2, 2, 2, 108, 110, 3, 2, 2, 2, 109, 105, 3, 2, 2, 2, 110, 111, 3, 2,
+	2, 2, 111, 109, 3, 2, 2, 2, 111, 112, 3, 2, 2, 2, 112, 37, 3, 2, 2, 2,
+	113, 114, 9, 5, 2, 2, 114, 39, 3, 2, 2, 2, 115, 116, 9, 6, 2, 2, 116, 41,
+	3, 2, 2, 2, 8, 56, 65, 75, 85, 107, 111,
+}
+var literalNames = []string{
+	"", "'has'", "'('", "')'", "'not'", "'empty'", "'now'", "'seconds'", "'days'",
+	"'months'", "'in'", "'any'", "'all'", "'within'", "','", "'=='", "'!='",
+	"'<='", "'<'", "'>'", "'>='", "'contains'", "'before'", "'after'", "'younger'",
+	"'older'", "", "'true'", "'false'",
+}
+var symbolicNames = []string{
+	"", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "EqualsOperator",
+	"NotEqualsOperator", "LessOrEqualsThanOperator", "LessThanOperator", "MoreThanOperator",
+	"MoreOrEqualsThanOperator", "ContainsOperator", "BeforeOperator", "AfterOperator",
+	"YoungerOperator", "OlderOperator", "BooleanLiteral", "True", "False",
+	"Identifier", "IntNumber", "FloatNumber", "StringLiteral", "Whitespace",
+}
+
+var ruleNames = []string{
+	"condition", "assetType", "simpleAssetType", "field", "expression", "simpleExpression",
+	"notExpression", "isEmptyExpression", "comparison", "binaryComparison",
+	"timeComparison", "timeOperator", "nowOperator", "duration", "unit", "inExpression",
+	"scope", "withinExpression", "value", "operator",
+}
+
+type CCLParser struct {
+	*antlr.BaseParser
+}
+
+// NewCCLParser produces a new parser instance for the optional input antlr.TokenStream.
+//
+// The *CCLParser instance produced may be reused by calling the SetInputStream method.
+// The initial parser configuration is expensive to construct, and the object is not thread-safe;
+// however, if used within a Golang sync.Pool, the construction cost amortizes well and the
+// objects can be used in a thread-safe manner.
+func NewCCLParser(input antlr.TokenStream) *CCLParser {
+	this := new(CCLParser)
+	deserializer := antlr.NewATNDeserializer(nil)
+	deserializedATN := deserializer.DeserializeFromUInt16(parserATN)
+	decisionToDFA := make([]*antlr.DFA, len(deserializedATN.DecisionToState))
+	for index, ds := range deserializedATN.DecisionToState {
+		decisionToDFA[index] = antlr.NewDFA(ds, index)
+	}
+	this.BaseParser = antlr.NewBaseParser(input)
+
+	this.Interpreter = antlr.NewParserATNSimulator(this, deserializedATN, decisionToDFA, antlr.NewPredictionContextCache())
+	this.RuleNames = ruleNames
+	this.LiteralNames = literalNames
+	this.SymbolicNames = symbolicNames
+	this.GrammarFileName = "CCL.g4"
+
+	return this
+}
+
+// CCLParser tokens.
+const (
+	CCLParserEOF                      = antlr.TokenEOF
+	CCLParserT__0                     = 1
+	CCLParserT__1                     = 2
+	CCLParserT__2                     = 3
+	CCLParserT__3                     = 4
+	CCLParserT__4                     = 5
+	CCLParserT__5                     = 6
+	CCLParserT__6                     = 7
+	CCLParserT__7                     = 8
+	CCLParserT__8                     = 9
+	CCLParserT__9                     = 10
+	CCLParserT__10                    = 11
+	CCLParserT__11                    = 12
+	CCLParserT__12                    = 13
+	CCLParserT__13                    = 14
+	CCLParserEqualsOperator           = 15
+	CCLParserNotEqualsOperator        = 16
+	CCLParserLessOrEqualsThanOperator = 17
+	CCLParserLessThanOperator         = 18
+	CCLParserMoreThanOperator         = 19
+	CCLParserMoreOrEqualsThanOperator = 20
+	CCLParserContainsOperator         = 21
+	CCLParserBeforeOperator           = 22
+	CCLParserAfterOperator            = 23
+	CCLParserYoungerOperator          = 24
+	CCLParserOlderOperator            = 25
+	CCLParserBooleanLiteral           = 26
+	CCLParserTrue                     = 27
+	CCLParserFalse                    = 28
+	CCLParserIdentifier               = 29
+	CCLParserIntNumber                = 30
+	CCLParserFloatNumber              = 31
+	CCLParserStringLiteral            = 32
+	CCLParserWhitespace               = 33
+)
+
+// CCLParser rules.
+const (
+	CCLParserRULE_condition         = 0
+	CCLParserRULE_assetType         = 1
+	CCLParserRULE_simpleAssetType   = 2
+	CCLParserRULE_field             = 3
+	CCLParserRULE_expression        = 4
+	CCLParserRULE_simpleExpression  = 5
+	CCLParserRULE_notExpression     = 6
+	CCLParserRULE_isEmptyExpression = 7
+	CCLParserRULE_comparison        = 8
+	CCLParserRULE_binaryComparison  = 9
+	CCLParserRULE_timeComparison    = 10
+	CCLParserRULE_timeOperator      = 11
+	CCLParserRULE_nowOperator       = 12
+	CCLParserRULE_duration          = 13
+	CCLParserRULE_unit              = 14
+	CCLParserRULE_inExpression      = 15
+	CCLParserRULE_scope             = 16
+	CCLParserRULE_withinExpression  = 17
+	CCLParserRULE_value             = 18
+	CCLParserRULE_operator          = 19
+)
+
+// IConditionContext is an interface to support dynamic dispatch.
+type IConditionContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsConditionContext differentiates from other interfaces.
+	IsConditionContext()
+}
+
+type ConditionContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyConditionContext() *ConditionContext {
+	var p = new(ConditionContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = CCLParserRULE_condition
+	return p
+}
+
+func (*ConditionContext) IsConditionContext() {}
+
+func NewConditionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ConditionContext {
+	var p = new(ConditionContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = CCLParserRULE_condition
+
+	return p
+}
+
+func (s *ConditionContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *ConditionContext) AssetType() IAssetTypeContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IAssetTypeContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IAssetTypeContext)
+}
+
+func (s *ConditionContext) Expression() IExpressionContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IExpressionContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IExpressionContext)
+}
+
+func (s *ConditionContext) EOF() antlr.TerminalNode {
+	return s.GetToken(CCLParserEOF, 0)
+}
+
+func (s *ConditionContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *ConditionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (p *CCLParser) Condition() (localctx IConditionContext) {
+	localctx = NewConditionContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 0, CCLParserRULE_condition)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(40)
+		p.AssetType()
+	}
+	{
+		p.SetState(41)
+		p.Match(CCLParserT__0)
+	}
+	{
+		p.SetState(42)
+		p.Expression()
+	}
+	{
+		p.SetState(43)
+		p.Match(CCLParserEOF)
+	}
+
+	return localctx
+}
+
+// IAssetTypeContext is an interface to support dynamic dispatch.
+type IAssetTypeContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsAssetTypeContext differentiates from other interfaces.
+	IsAssetTypeContext()
+}
+
+type AssetTypeContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyAssetTypeContext() *AssetTypeContext {
+	var p = new(AssetTypeContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = CCLParserRULE_assetType
+	return p
+}
+
+func (*AssetTypeContext) IsAssetTypeContext() {}
+
+func NewAssetTypeContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *AssetTypeContext {
+	var p = new(AssetTypeContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = CCLParserRULE_assetType
+
+	return p
+}
+
+func (s *AssetTypeContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *AssetTypeContext) SimpleAssetType() ISimpleAssetTypeContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*ISimpleAssetTypeContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(ISimpleAssetTypeContext)
+}
+
+func (s *AssetTypeContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *AssetTypeContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (p *CCLParser) AssetType() (localctx IAssetTypeContext) {
+	localctx = NewAssetTypeContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 2, CCLParserRULE_assetType)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(45)
+		p.SimpleAssetType()
+	}
+
+	return localctx
+}
+
+// ISimpleAssetTypeContext is an interface to support dynamic dispatch.
+type ISimpleAssetTypeContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsSimpleAssetTypeContext differentiates from other interfaces.
+	IsSimpleAssetTypeContext()
+}
+
+type SimpleAssetTypeContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptySimpleAssetTypeContext() *SimpleAssetTypeContext {
+	var p = new(SimpleAssetTypeContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = CCLParserRULE_simpleAssetType
+	return p
+}
+
+func (*SimpleAssetTypeContext) IsSimpleAssetTypeContext() {}
+
+func NewSimpleAssetTypeContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *SimpleAssetTypeContext {
+	var p = new(SimpleAssetTypeContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = CCLParserRULE_simpleAssetType
+
+	return p
+}
+
+func (s *SimpleAssetTypeContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *SimpleAssetTypeContext) Field() IFieldContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IFieldContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IFieldContext)
+}
+
+func (s *SimpleAssetTypeContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *SimpleAssetTypeContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (p *CCLParser) SimpleAssetType() (localctx ISimpleAssetTypeContext) {
+	localctx = NewSimpleAssetTypeContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 4, CCLParserRULE_simpleAssetType)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(47)
+		p.Field()
+	}
+
+	return localctx
+}
+
+// IFieldContext is an interface to support dynamic dispatch.
+type IFieldContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsFieldContext differentiates from other interfaces.
+	IsFieldContext()
+}
+
+type FieldContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyFieldContext() *FieldContext {
+	var p = new(FieldContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = CCLParserRULE_field
+	return p
+}
+
+func (*FieldContext) IsFieldContext() {}
+
+func NewFieldContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *FieldContext {
+	var p = new(FieldContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = CCLParserRULE_field
+
+	return p
+}
+
+func (s *FieldContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *FieldContext) Identifier() antlr.TerminalNode {
+	return s.GetToken(CCLParserIdentifier, 0)
+}
+
+func (s *FieldContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *FieldContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (p *CCLParser) Field() (localctx IFieldContext) {
+	localctx = NewFieldContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 6, CCLParserRULE_field)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(49)
+		p.Match(CCLParserIdentifier)
+	}
+
+	return localctx
+}
+
+// IExpressionContext is an interface to support dynamic dispatch.
+type IExpressionContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsExpressionContext differentiates from other interfaces.
+	IsExpressionContext()
+}
+
+type ExpressionContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyExpressionContext() *ExpressionContext {
+	var p = new(ExpressionContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = CCLParserRULE_expression
+	return p
+}
+
+func (*ExpressionContext) IsExpressionContext() {}
+
+func NewExpressionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ExpressionContext {
+	var p = new(ExpressionContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = CCLParserRULE_expression
+
+	return p
+}
+
+func (s *ExpressionContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *ExpressionContext) SimpleExpression() ISimpleExpressionContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*ISimpleExpressionContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(ISimpleExpressionContext)
+}
+
+func (s *ExpressionContext) NotExpression() INotExpressionContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*INotExpressionContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(INotExpressionContext)
+}
+
+func (s *ExpressionContext) InExpression() IInExpressionContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IInExpressionContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IInExpressionContext)
+}
+
+func (s *ExpressionContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *ExpressionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (p *CCLParser) Expression() (localctx IExpressionContext) {
+	localctx = NewExpressionContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 8, CCLParserRULE_expression)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.SetState(54)
+	p.GetErrorHandler().Sync(p)
+	switch p.GetInterpreter().AdaptivePredict(p.GetTokenStream(), 0, p.GetParserRuleContext()) {
+	case 1:
+		p.EnterOuterAlt(localctx, 1)
+		{
+			p.SetState(51)
+			p.SimpleExpression()
+		}
+
+	case 2:
+		p.EnterOuterAlt(localctx, 2)
+		{
+			p.SetState(52)
+			p.NotExpression()
+		}
+
+	case 3:
+		p.EnterOuterAlt(localctx, 3)
+		{
+			p.SetState(53)
+			p.InExpression()
+		}
+
+	}
+
+	return localctx
+}
+
+// ISimpleExpressionContext is an interface to support dynamic dispatch.
+type ISimpleExpressionContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsSimpleExpressionContext differentiates from other interfaces.
+	IsSimpleExpressionContext()
+}
+
+type SimpleExpressionContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptySimpleExpressionContext() *SimpleExpressionContext {
+	var p = new(SimpleExpressionContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = CCLParserRULE_simpleExpression
+	return p
+}
+
+func (*SimpleExpressionContext) IsSimpleExpressionContext() {}
+
+func NewSimpleExpressionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *SimpleExpressionContext {
+	var p = new(SimpleExpressionContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = CCLParserRULE_simpleExpression
+
+	return p
+}
+
+func (s *SimpleExpressionContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *SimpleExpressionContext) IsEmptyExpression() IIsEmptyExpressionContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IIsEmptyExpressionContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IIsEmptyExpressionContext)
+}
+
+func (s *SimpleExpressionContext) WithinExpression() IWithinExpressionContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IWithinExpressionContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IWithinExpressionContext)
+}
+
+func (s *SimpleExpressionContext) Comparison() IComparisonContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IComparisonContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IComparisonContext)
+}
+
+func (s *SimpleExpressionContext) Expression() IExpressionContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IExpressionContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IExpressionContext)
+}
+
+func (s *SimpleExpressionContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *SimpleExpressionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (p *CCLParser) SimpleExpression() (localctx ISimpleExpressionContext) {
+	localctx = NewSimpleExpressionContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 10, CCLParserRULE_simpleExpression)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.SetState(63)
+	p.GetErrorHandler().Sync(p)
+	switch p.GetInterpreter().AdaptivePredict(p.GetTokenStream(), 1, p.GetParserRuleContext()) {
+	case 1:
+		p.EnterOuterAlt(localctx, 1)
+		{
+			p.SetState(56)
+			p.IsEmptyExpression()
+		}
+
+	case 2:
+		p.EnterOuterAlt(localctx, 2)
+		{
+			p.SetState(57)
+			p.WithinExpression()
+		}
+
+	case 3:
+		p.EnterOuterAlt(localctx, 3)
+		{
+			p.SetState(58)
+			p.Comparison()
+		}
+
+	case 4:
+		p.EnterOuterAlt(localctx, 4)
+		{
+			p.SetState(59)
+			p.Match(CCLParserT__1)
+		}
+		{
+			p.SetState(60)
+			p.Expression()
+		}
+		{
+			p.SetState(61)
+			p.Match(CCLParserT__2)
+		}
+
+	}
+
+	return localctx
+}
+
+// INotExpressionContext is an interface to support dynamic dispatch.
+type INotExpressionContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsNotExpressionContext differentiates from other interfaces.
+	IsNotExpressionContext()
+}
+
+type NotExpressionContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyNotExpressionContext() *NotExpressionContext {
+	var p = new(NotExpressionContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = CCLParserRULE_notExpression
+	return p
+}
+
+func (*NotExpressionContext) IsNotExpressionContext() {}
+
+func NewNotExpressionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *NotExpressionContext {
+	var p = new(NotExpressionContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = CCLParserRULE_notExpression
+
+	return p
+}
+
+func (s *NotExpressionContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *NotExpressionContext) Expression() IExpressionContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IExpressionContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IExpressionContext)
+}
+
+func (s *NotExpressionContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *NotExpressionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (p *CCLParser) NotExpression() (localctx INotExpressionContext) {
+	localctx = NewNotExpressionContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 12, CCLParserRULE_notExpression)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(65)
+		p.Match(CCLParserT__3)
+	}
+	{
+		p.SetState(66)
+		p.Expression()
+	}
+
+	return localctx
+}
+
+// IIsEmptyExpressionContext is an interface to support dynamic dispatch.
+type IIsEmptyExpressionContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsIsEmptyExpressionContext differentiates from other interfaces.
+	IsIsEmptyExpressionContext()
+}
+
+type IsEmptyExpressionContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyIsEmptyExpressionContext() *IsEmptyExpressionContext {
+	var p = new(IsEmptyExpressionContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = CCLParserRULE_isEmptyExpression
+	return p
+}
+
+func (*IsEmptyExpressionContext) IsIsEmptyExpressionContext() {}
+
+func NewIsEmptyExpressionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *IsEmptyExpressionContext {
+	var p = new(IsEmptyExpressionContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = CCLParserRULE_isEmptyExpression
+
+	return p
+}
+
+func (s *IsEmptyExpressionContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *IsEmptyExpressionContext) Field() IFieldContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IFieldContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IFieldContext)
+}
+
+func (s *IsEmptyExpressionContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *IsEmptyExpressionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (p *CCLParser) IsEmptyExpression() (localctx IIsEmptyExpressionContext) {
+	localctx = NewIsEmptyExpressionContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 14, CCLParserRULE_isEmptyExpression)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(68)
+		p.Match(CCLParserT__4)
+	}
+	{
+		p.SetState(69)
+		p.Field()
+	}
+
+	return localctx
+}
+
+// IComparisonContext is an interface to support dynamic dispatch.
+type IComparisonContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsComparisonContext differentiates from other interfaces.
+	IsComparisonContext()
+}
+
+type ComparisonContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyComparisonContext() *ComparisonContext {
+	var p = new(ComparisonContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = CCLParserRULE_comparison
+	return p
+}
+
+func (*ComparisonContext) IsComparisonContext() {}
+
+func NewComparisonContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ComparisonContext {
+	var p = new(ComparisonContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = CCLParserRULE_comparison
+
+	return p
+}
+
+func (s *ComparisonContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *ComparisonContext) BinaryComparison() IBinaryComparisonContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IBinaryComparisonContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IBinaryComparisonContext)
+}
+
+func (s *ComparisonContext) TimeComparison() ITimeComparisonContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*ITimeComparisonContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(ITimeComparisonContext)
+}
+
+func (s *ComparisonContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *ComparisonContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (p *CCLParser) Comparison() (localctx IComparisonContext) {
+	localctx = NewComparisonContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 16, CCLParserRULE_comparison)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.SetState(73)
+	p.GetErrorHandler().Sync(p)
+	switch p.GetInterpreter().AdaptivePredict(p.GetTokenStream(), 2, p.GetParserRuleContext()) {
+	case 1:
+		p.EnterOuterAlt(localctx, 1)
+		{
+			p.SetState(71)
+			p.BinaryComparison()
+		}
+
+	case 2:
+		p.EnterOuterAlt(localctx, 2)
+		{
+			p.SetState(72)
+			p.TimeComparison()
+		}
+
+	}
+
+	return localctx
+}
+
+// IBinaryComparisonContext is an interface to support dynamic dispatch.
+type IBinaryComparisonContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsBinaryComparisonContext differentiates from other interfaces.
+	IsBinaryComparisonContext()
+}
+
+type BinaryComparisonContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyBinaryComparisonContext() *BinaryComparisonContext {
+	var p = new(BinaryComparisonContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = CCLParserRULE_binaryComparison
+	return p
+}
+
+func (*BinaryComparisonContext) IsBinaryComparisonContext() {}
+
+func NewBinaryComparisonContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *BinaryComparisonContext {
+	var p = new(BinaryComparisonContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = CCLParserRULE_binaryComparison
+
+	return p
+}
+
+func (s *BinaryComparisonContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *BinaryComparisonContext) Field() IFieldContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IFieldContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IFieldContext)
+}
+
+func (s *BinaryComparisonContext) Operator() IOperatorContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IOperatorContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IOperatorContext)
+}
+
+func (s *BinaryComparisonContext) Value() IValueContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IValueContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IValueContext)
+}
+
+func (s *BinaryComparisonContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *BinaryComparisonContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (p *CCLParser) BinaryComparison() (localctx IBinaryComparisonContext) {
+	localctx = NewBinaryComparisonContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 18, CCLParserRULE_binaryComparison)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(75)
+		p.Field()
+	}
+	{
+		p.SetState(76)
+		p.Operator()
+	}
+	{
+		p.SetState(77)
+		p.Value()
+	}
+
+	return localctx
+}
+
+// ITimeComparisonContext is an interface to support dynamic dispatch.
+type ITimeComparisonContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsTimeComparisonContext differentiates from other interfaces.
+	IsTimeComparisonContext()
+}
+
+type TimeComparisonContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyTimeComparisonContext() *TimeComparisonContext {
+	var p = new(TimeComparisonContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = CCLParserRULE_timeComparison
+	return p
+}
+
+func (*TimeComparisonContext) IsTimeComparisonContext() {}
+
+func NewTimeComparisonContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TimeComparisonContext {
+	var p = new(TimeComparisonContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = CCLParserRULE_timeComparison
+
+	return p
+}
+
+func (s *TimeComparisonContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *TimeComparisonContext) Field() IFieldContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IFieldContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IFieldContext)
+}
+
+func (s *TimeComparisonContext) TimeOperator() ITimeOperatorContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*ITimeOperatorContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(ITimeOperatorContext)
+}
+
+func (s *TimeComparisonContext) Duration() IDurationContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IDurationContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IDurationContext)
+}
+
+func (s *TimeComparisonContext) NowOperator() INowOperatorContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*INowOperatorContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(INowOperatorContext)
+}
+
+func (s *TimeComparisonContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *TimeComparisonContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (p *CCLParser) TimeComparison() (localctx ITimeComparisonContext) {
+	localctx = NewTimeComparisonContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 20, CCLParserRULE_timeComparison)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(79)
+		p.Field()
+	}
+	{
+		p.SetState(80)
+		p.TimeOperator()
+	}
+	p.SetState(83)
+	p.GetErrorHandler().Sync(p)
+
+	switch p.GetTokenStream().LA(1) {
+	case CCLParserIntNumber:
+		{
+			p.SetState(81)
+			p.Duration()
+		}
+
+	case CCLParserT__5:
+		{
+			p.SetState(82)
+			p.NowOperator()
+		}
+
+	default:
+		panic(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil))
+	}
+
+	return localctx
+}
+
+// ITimeOperatorContext is an interface to support dynamic dispatch.
+type ITimeOperatorContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsTimeOperatorContext differentiates from other interfaces.
+	IsTimeOperatorContext()
+}
+
+type TimeOperatorContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyTimeOperatorContext() *TimeOperatorContext {
+	var p = new(TimeOperatorContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = CCLParserRULE_timeOperator
+	return p
+}
+
+func (*TimeOperatorContext) IsTimeOperatorContext() {}
+
+func NewTimeOperatorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *TimeOperatorContext {
+	var p = new(TimeOperatorContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = CCLParserRULE_timeOperator
+
+	return p
+}
+
+func (s *TimeOperatorContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *TimeOperatorContext) BeforeOperator() antlr.TerminalNode {
+	return s.GetToken(CCLParserBeforeOperator, 0)
+}
+
+func (s *TimeOperatorContext) AfterOperator() antlr.TerminalNode {
+	return s.GetToken(CCLParserAfterOperator, 0)
+}
+
+func (s *TimeOperatorContext) YoungerOperator() antlr.TerminalNode {
+	return s.GetToken(CCLParserYoungerOperator, 0)
+}
+
+func (s *TimeOperatorContext) OlderOperator() antlr.TerminalNode {
+	return s.GetToken(CCLParserOlderOperator, 0)
+}
+
+func (s *TimeOperatorContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *TimeOperatorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (p *CCLParser) TimeOperator() (localctx ITimeOperatorContext) {
+	localctx = NewTimeOperatorContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 22, CCLParserRULE_timeOperator)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(85)
+		_la = p.GetTokenStream().LA(1)
+
+		if !(((_la)&-(0x1f+1)) == 0 && ((1<<uint(_la))&((1<<CCLParserBeforeOperator)|(1<<CCLParserAfterOperator)|(1<<CCLParserYoungerOperator)|(1<<CCLParserOlderOperator))) != 0) {
+			p.GetErrorHandler().RecoverInline(p)
+		} else {
+			p.GetErrorHandler().ReportMatch(p)
+			p.Consume()
+		}
+	}
+
+	return localctx
+}
+
+// INowOperatorContext is an interface to support dynamic dispatch.
+type INowOperatorContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsNowOperatorContext differentiates from other interfaces.
+	IsNowOperatorContext()
+}
+
+type NowOperatorContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyNowOperatorContext() *NowOperatorContext {
+	var p = new(NowOperatorContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = CCLParserRULE_nowOperator
+	return p
+}
+
+func (*NowOperatorContext) IsNowOperatorContext() {}
+
+func NewNowOperatorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *NowOperatorContext {
+	var p = new(NowOperatorContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = CCLParserRULE_nowOperator
+
+	return p
+}
+
+func (s *NowOperatorContext) GetParser() antlr.Parser { return s.parser }
+func (s *NowOperatorContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *NowOperatorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (p *CCLParser) NowOperator() (localctx INowOperatorContext) {
+	localctx = NewNowOperatorContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 24, CCLParserRULE_nowOperator)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(87)
+		p.Match(CCLParserT__5)
+	}
+
+	return localctx
+}
+
+// IDurationContext is an interface to support dynamic dispatch.
+type IDurationContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsDurationContext differentiates from other interfaces.
+	IsDurationContext()
+}
+
+type DurationContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyDurationContext() *DurationContext {
+	var p = new(DurationContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = CCLParserRULE_duration
+	return p
+}
+
+func (*DurationContext) IsDurationContext() {}
+
+func NewDurationContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *DurationContext {
+	var p = new(DurationContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = CCLParserRULE_duration
+
+	return p
+}
+
+func (s *DurationContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *DurationContext) IntNumber() antlr.TerminalNode {
+	return s.GetToken(CCLParserIntNumber, 0)
+}
+
+func (s *DurationContext) Unit() IUnitContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IUnitContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IUnitContext)
+}
+
+func (s *DurationContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *DurationContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (p *CCLParser) Duration() (localctx IDurationContext) {
+	localctx = NewDurationContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 26, CCLParserRULE_duration)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(89)
+		p.Match(CCLParserIntNumber)
+	}
+	{
+		p.SetState(90)
+		p.Unit()
+	}
+
+	return localctx
+}
+
+// IUnitContext is an interface to support dynamic dispatch.
+type IUnitContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsUnitContext differentiates from other interfaces.
+	IsUnitContext()
+}
+
+type UnitContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyUnitContext() *UnitContext {
+	var p = new(UnitContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = CCLParserRULE_unit
+	return p
+}
+
+func (*UnitContext) IsUnitContext() {}
+
+func NewUnitContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *UnitContext {
+	var p = new(UnitContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = CCLParserRULE_unit
+
+	return p
+}
+
+func (s *UnitContext) GetParser() antlr.Parser { return s.parser }
+func (s *UnitContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *UnitContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (p *CCLParser) Unit() (localctx IUnitContext) {
+	localctx = NewUnitContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 28, CCLParserRULE_unit)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(92)
+		_la = p.GetTokenStream().LA(1)
+
+		if !(((_la)&-(0x1f+1)) == 0 && ((1<<uint(_la))&((1<<CCLParserT__6)|(1<<CCLParserT__7)|(1<<CCLParserT__8))) != 0) {
+			p.GetErrorHandler().RecoverInline(p)
+		} else {
+			p.GetErrorHandler().ReportMatch(p)
+			p.Consume()
+		}
+	}
+
+	return localctx
+}
+
+// IInExpressionContext is an interface to support dynamic dispatch.
+type IInExpressionContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsInExpressionContext differentiates from other interfaces.
+	IsInExpressionContext()
+}
+
+type InExpressionContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyInExpressionContext() *InExpressionContext {
+	var p = new(InExpressionContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = CCLParserRULE_inExpression
+	return p
+}
+
+func (*InExpressionContext) IsInExpressionContext() {}
+
+func NewInExpressionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *InExpressionContext {
+	var p = new(InExpressionContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = CCLParserRULE_inExpression
+
+	return p
+}
+
+func (s *InExpressionContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *InExpressionContext) SimpleExpression() ISimpleExpressionContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*ISimpleExpressionContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(ISimpleExpressionContext)
+}
+
+func (s *InExpressionContext) Scope() IScopeContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IScopeContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IScopeContext)
+}
+
+func (s *InExpressionContext) Field() IFieldContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IFieldContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IFieldContext)
+}
+
+func (s *InExpressionContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *InExpressionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (p *CCLParser) InExpression() (localctx IInExpressionContext) {
+	localctx = NewInExpressionContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 30, CCLParserRULE_inExpression)
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(94)
+		p.SimpleExpression()
+	}
+	{
+		p.SetState(95)
+		p.Match(CCLParserT__9)
+	}
+	{
+		p.SetState(96)
+		p.Scope()
+	}
+	{
+		p.SetState(97)
+		p.Field()
+	}
+
+	return localctx
+}
+
+// IScopeContext is an interface to support dynamic dispatch.
+type IScopeContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsScopeContext differentiates from other interfaces.
+	IsScopeContext()
+}
+
+type ScopeContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyScopeContext() *ScopeContext {
+	var p = new(ScopeContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = CCLParserRULE_scope
+	return p
+}
+
+func (*ScopeContext) IsScopeContext() {}
+
+func NewScopeContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ScopeContext {
+	var p = new(ScopeContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = CCLParserRULE_scope
+
+	return p
+}
+
+func (s *ScopeContext) GetParser() antlr.Parser { return s.parser }
+func (s *ScopeContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *ScopeContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (p *CCLParser) Scope() (localctx IScopeContext) {
+	localctx = NewScopeContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 32, CCLParserRULE_scope)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(99)
+		_la = p.GetTokenStream().LA(1)
+
+		if !(_la == CCLParserT__10 || _la == CCLParserT__11) {
+			p.GetErrorHandler().RecoverInline(p)
+		} else {
+			p.GetErrorHandler().ReportMatch(p)
+			p.Consume()
+		}
+	}
+
+	return localctx
+}
+
+// IWithinExpressionContext is an interface to support dynamic dispatch.
+type IWithinExpressionContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsWithinExpressionContext differentiates from other interfaces.
+	IsWithinExpressionContext()
+}
+
+type WithinExpressionContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyWithinExpressionContext() *WithinExpressionContext {
+	var p = new(WithinExpressionContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = CCLParserRULE_withinExpression
+	return p
+}
+
+func (*WithinExpressionContext) IsWithinExpressionContext() {}
+
+func NewWithinExpressionContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *WithinExpressionContext {
+	var p = new(WithinExpressionContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = CCLParserRULE_withinExpression
+
+	return p
+}
+
+func (s *WithinExpressionContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *WithinExpressionContext) Field() IFieldContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IFieldContext)(nil)).Elem(), 0)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IFieldContext)
+}
+
+func (s *WithinExpressionContext) AllValue() []IValueContext {
+	var ts = s.GetTypedRuleContexts(reflect.TypeOf((*IValueContext)(nil)).Elem())
+	var tst = make([]IValueContext, len(ts))
+
+	for i, t := range ts {
+		if t != nil {
+			tst[i] = t.(IValueContext)
+		}
+	}
+
+	return tst
+}
+
+func (s *WithinExpressionContext) Value(i int) IValueContext {
+	var t = s.GetTypedRuleContext(reflect.TypeOf((*IValueContext)(nil)).Elem(), i)
+
+	if t == nil {
+		return nil
+	}
+
+	return t.(IValueContext)
+}
+
+func (s *WithinExpressionContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *WithinExpressionContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (p *CCLParser) WithinExpression() (localctx IWithinExpressionContext) {
+	localctx = NewWithinExpressionContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 34, CCLParserRULE_withinExpression)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(101)
+		p.Field()
+	}
+	{
+		p.SetState(102)
+		p.Match(CCLParserT__12)
+	}
+	p.SetState(107)
+	p.GetErrorHandler().Sync(p)
+	_la = p.GetTokenStream().LA(1)
+
+	for ok := true; ok; ok = (((_la-26)&-(0x1f+1)) == 0 && ((1<<uint((_la-26)))&((1<<(CCLParserBooleanLiteral-26))|(1<<(CCLParserIntNumber-26))|(1<<(CCLParserFloatNumber-26))|(1<<(CCLParserStringLiteral-26)))) != 0) {
+		{
+			p.SetState(103)
+			p.Value()
+		}
+		p.SetState(105)
+		p.GetErrorHandler().Sync(p)
+		_la = p.GetTokenStream().LA(1)
+
+		if _la == CCLParserT__13 {
+			{
+				p.SetState(104)
+				p.Match(CCLParserT__13)
+			}
+
+		}
+
+		p.SetState(109)
+		p.GetErrorHandler().Sync(p)
+		_la = p.GetTokenStream().LA(1)
+	}
+
+	return localctx
+}
+
+// IValueContext is an interface to support dynamic dispatch.
+type IValueContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsValueContext differentiates from other interfaces.
+	IsValueContext()
+}
+
+type ValueContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyValueContext() *ValueContext {
+	var p = new(ValueContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = CCLParserRULE_value
+	return p
+}
+
+func (*ValueContext) IsValueContext() {}
+
+func NewValueContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *ValueContext {
+	var p = new(ValueContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = CCLParserRULE_value
+
+	return p
+}
+
+func (s *ValueContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *ValueContext) StringLiteral() antlr.TerminalNode {
+	return s.GetToken(CCLParserStringLiteral, 0)
+}
+
+func (s *ValueContext) BooleanLiteral() antlr.TerminalNode {
+	return s.GetToken(CCLParserBooleanLiteral, 0)
+}
+
+func (s *ValueContext) IntNumber() antlr.TerminalNode {
+	return s.GetToken(CCLParserIntNumber, 0)
+}
+
+func (s *ValueContext) FloatNumber() antlr.TerminalNode {
+	return s.GetToken(CCLParserFloatNumber, 0)
+}
+
+func (s *ValueContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *ValueContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (p *CCLParser) Value() (localctx IValueContext) {
+	localctx = NewValueContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 36, CCLParserRULE_value)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(111)
+		_la = p.GetTokenStream().LA(1)
+
+		if !(((_la-26)&-(0x1f+1)) == 0 && ((1<<uint((_la-26)))&((1<<(CCLParserBooleanLiteral-26))|(1<<(CCLParserIntNumber-26))|(1<<(CCLParserFloatNumber-26))|(1<<(CCLParserStringLiteral-26)))) != 0) {
+			p.GetErrorHandler().RecoverInline(p)
+		} else {
+			p.GetErrorHandler().ReportMatch(p)
+			p.Consume()
+		}
+	}
+
+	return localctx
+}
+
+// IOperatorContext is an interface to support dynamic dispatch.
+type IOperatorContext interface {
+	antlr.ParserRuleContext
+
+	// GetParser returns the parser.
+	GetParser() antlr.Parser
+
+	// IsOperatorContext differentiates from other interfaces.
+	IsOperatorContext()
+}
+
+type OperatorContext struct {
+	*antlr.BaseParserRuleContext
+	parser antlr.Parser
+}
+
+func NewEmptyOperatorContext() *OperatorContext {
+	var p = new(OperatorContext)
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(nil, -1)
+	p.RuleIndex = CCLParserRULE_operator
+	return p
+}
+
+func (*OperatorContext) IsOperatorContext() {}
+
+func NewOperatorContext(parser antlr.Parser, parent antlr.ParserRuleContext, invokingState int) *OperatorContext {
+	var p = new(OperatorContext)
+
+	p.BaseParserRuleContext = antlr.NewBaseParserRuleContext(parent, invokingState)
+
+	p.parser = parser
+	p.RuleIndex = CCLParserRULE_operator
+
+	return p
+}
+
+func (s *OperatorContext) GetParser() antlr.Parser { return s.parser }
+
+func (s *OperatorContext) EqualsOperator() antlr.TerminalNode {
+	return s.GetToken(CCLParserEqualsOperator, 0)
+}
+
+func (s *OperatorContext) NotEqualsOperator() antlr.TerminalNode {
+	return s.GetToken(CCLParserNotEqualsOperator, 0)
+}
+
+func (s *OperatorContext) LessOrEqualsThanOperator() antlr.TerminalNode {
+	return s.GetToken(CCLParserLessOrEqualsThanOperator, 0)
+}
+
+func (s *OperatorContext) LessThanOperator() antlr.TerminalNode {
+	return s.GetToken(CCLParserLessThanOperator, 0)
+}
+
+func (s *OperatorContext) MoreThanOperator() antlr.TerminalNode {
+	return s.GetToken(CCLParserMoreThanOperator, 0)
+}
+
+func (s *OperatorContext) MoreOrEqualsThanOperator() antlr.TerminalNode {
+	return s.GetToken(CCLParserMoreOrEqualsThanOperator, 0)
+}
+
+func (s *OperatorContext) ContainsOperator() antlr.TerminalNode {
+	return s.GetToken(CCLParserContainsOperator, 0)
+}
+
+func (s *OperatorContext) GetRuleContext() antlr.RuleContext {
+	return s
+}
+
+func (s *OperatorContext) ToStringTree(ruleNames []string, recog antlr.Recognizer) string {
+	return antlr.TreesStringTree(s, ruleNames, recog)
+}
+
+func (p *CCLParser) Operator() (localctx IOperatorContext) {
+	localctx = NewOperatorContext(p, p.GetParserRuleContext(), p.GetState())
+	p.EnterRule(localctx, 38, CCLParserRULE_operator)
+	var _la int
+
+	defer func() {
+		p.ExitRule()
+	}()
+
+	defer func() {
+		if err := recover(); err != nil {
+			if v, ok := err.(antlr.RecognitionException); ok {
+				localctx.SetException(v)
+				p.GetErrorHandler().ReportError(p, v)
+				p.GetErrorHandler().Recover(p, v)
+			} else {
+				panic(err)
+			}
+		}
+	}()
+
+	p.EnterOuterAlt(localctx, 1)
+	{
+		p.SetState(113)
+		_la = p.GetTokenStream().LA(1)
+
+		if !(((_la)&-(0x1f+1)) == 0 && ((1<<uint(_la))&((1<<CCLParserEqualsOperator)|(1<<CCLParserNotEqualsOperator)|(1<<CCLParserLessOrEqualsThanOperator)|(1<<CCLParserLessThanOperator)|(1<<CCLParserMoreThanOperator)|(1<<CCLParserMoreOrEqualsThanOperator)|(1<<CCLParserContainsOperator))) != 0) {
+			p.GetErrorHandler().RecoverInline(p)
+		} else {
+			p.GetErrorHandler().ReportMatch(p)
+			p.Consume()
+		}
+	}
+
+	return localctx
+}
diff --git a/ccl/parser/parser.go b/ccl/parser/parser.go
new file mode 100644
index 00000000..f77d7a70
--- /dev/null
+++ b/ccl/parser/parser.go
@@ -0,0 +1,29 @@
+// Copyright 2021 Fraunhofer AISEC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//           $$\                           $$\ $$\   $$\
+//           $$ |                          $$ |\__|  $$ |
+//  $$$$$$$\ $$ | $$$$$$\  $$\   $$\  $$$$$$$ |$$\ $$$$$$\    $$$$$$\   $$$$$$\
+// $$  _____|$$ |$$  __$$\ $$ |  $$ |$$  __$$ |$$ |\_$$  _|  $$  __$$\ $$  __$$\
+// $$ /      $$ |$$ /  $$ |$$ |  $$ |$$ /  $$ |$$ |  $$ |    $$ /  $$ |$$ | \__|
+// $$ |      $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$\ $$ |  $$ |$$ |
+// \$$$$$$\  $$ |\$$$$$   |\$$$$$   |\$$$$$$  |$$ |  \$$$   |\$$$$$   |$$ |
+//  \_______|\__| \______/  \______/  \_______|\__|   \____/  \______/ \__|
+//
+// This file is part of Clouditor Community Edition.
+
+// //go:generate antlr -Dlanguage=Go -no-listener CCL.g4
+
+// Package parser contains the files for the CCL Parser
+package parser
diff --git a/cmd/engine/engine.go b/cmd/engine/engine.go
index 5bfa5257..dc436ac4 100644
--- a/cmd/engine/engine.go
+++ b/cmd/engine/engine.go
@@ -227,7 +227,6 @@ func doCmd(cmd *cobra.Command, args []string) (err error) {
 
 func main() {
 	if err := engineCmd.Execute(); err != nil {
-		log.Panicf("Fatal error: %s", err)
 		os.Exit(1)
 	}
 }
diff --git a/go.mod b/go.mod
index 12de48b7..450b2f8c 100644
--- a/go.mod
+++ b/go.mod
@@ -8,6 +8,7 @@ require (
 	github.com/Azure/go-autorest/autorest/to v0.4.0 // indirect
 	github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
 	github.com/alexedwards/argon2id v0.0.0-20210511081203-7d35d68092b8
+	github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521184019-c5ad59b459ec
 	github.com/golang-jwt/jwt v0.0.0-20210529135444-590e8c64cd52
 	github.com/google/addlicense v0.0.0-20210428195630-6d92264d7170
 	github.com/googleapis/gnostic v0.5.6-0.20210520165051-0320d74b3646
@@ -17,7 +18,7 @@ require (
 	github.com/sirupsen/logrus v1.8.1
 	github.com/spf13/cobra v1.1.3
 	github.com/spf13/viper v1.7.1
-	github.com/stretchr/testify v1.7.0 // indirect
+	github.com/stretchr/testify v1.7.0
 	golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect
 	google.golang.org/genproto v0.0.0-20210524171403-669157292da3
 	google.golang.org/grpc v1.38.0
diff --git a/go.sum b/go.sum
index 13efadc4..2791c9f8 100644
--- a/go.sum
+++ b/go.sum
@@ -32,8 +32,6 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
 cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
 cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
-github.com/Azure/azure-sdk-for-go v54.3.0+incompatible h1:aJ/WT32eVP8YmWpuSHLgnFJWjZzUFmhR3wBxxszo4PE=
-github.com/Azure/azure-sdk-for-go v54.3.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
 github.com/Azure/azure-sdk-for-go v55.0.0+incompatible h1:L4/vUGbg1Xkw5L20LZD+hJI5I+ibWSytqQ68lTCfLwY=
 github.com/Azure/azure-sdk-for-go v55.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
 github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs=
@@ -77,6 +75,8 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
 github.com/alexedwards/argon2id v0.0.0-20210511081203-7d35d68092b8 h1:ZtMr6/tt7VU/Ijpyyedn7eUwwsNX1uskEcR+maLEF18=
 github.com/alexedwards/argon2id v0.0.0-20210511081203-7d35d68092b8/go.mod h1:Kmn5t2Rb93Q4NTprN4+CCgARGvigKMJyxP0WckpTUp0=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521184019-c5ad59b459ec h1:EEyRvzmpEUZ+I8WmD5cw/vY8EqhambkOqy5iFr0908A=
+github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521184019-c5ad59b459ec/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
 github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
@@ -124,8 +124,6 @@ github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7Do
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
-github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
 github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
 github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
@@ -173,7 +171,6 @@ github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang-jwt/jwt v0.0.0-20210527125010-9e96e9651418 h1:enLluoZtnoPIP9kdHOySLQsLr341Lmhx2XG1vKbEp8U=
 github.com/golang-jwt/jwt v0.0.0-20210527125010-9e96e9651418/go.mod h1:+p//S9doV/bQgQAIccnJTnjzzXfMMFPys/33QTsH8ks=
 github.com/golang-jwt/jwt v0.0.0-20210529135444-590e8c64cd52 h1:DPvygD0ZTQ+n+ZzPQRfGAqJ4LVt4cYcLl/hU884rX1k=
 github.com/golang-jwt/jwt v0.0.0-20210529135444-590e8c64cd52/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
@@ -613,7 +610,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
 golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
 golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI=
 golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
@@ -622,7 +618,6 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -793,7 +788,6 @@ golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc
 golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
 golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@@ -860,8 +854,6 @@ google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6D
 google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210207032614-bba0dbe2a9ea/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
 google.golang.org/genproto v0.0.0-20210426193834-eac7f76ac494/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
-google.golang.org/genproto v0.0.0-20210521181308-5ccab8a35a9a h1:FaCiYXNZoBH/gnmVjMAHgOgdmpVVROBYOA+qCOHh6Hc=
-google.golang.org/genproto v0.0.0-20210521181308-5ccab8a35a9a/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 google.golang.org/genproto v0.0.0-20210524171403-669157292da3 h1:xFyh6GBb+NO1L0xqb978I3sBPQpk6FrKO0jJGRvdj/0=
 google.golang.org/genproto v0.0.0-20210524171403-669157292da3/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A=
 google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
diff --git a/rules/encryption.ccl b/rules/encryption.ccl
new file mode 100644
index 00000000..a446ec09
--- /dev/null
+++ b/rules/encryption.ccl
@@ -0,0 +1,1 @@
+ObjectStorage has atRestEncryption.enabled == true
\ No newline at end of file


Test patch:
diff --git a/ccl/ccl_test.go b/ccl/ccl_test.go
new file mode 100644
index 00000000..8989b484
--- /dev/null
+++ b/ccl/ccl_test.go
@@ -0,0 +1,272 @@
+// Copyright 2021 Fraunhofer AISEC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+//           $$\                           $$\ $$\   $$\
+//           $$ |                          $$ |\__|  $$ |
+//  $$$$$$$\ $$ | $$$$$$\  $$\   $$\  $$$$$$$ |$$\ $$$$$$\    $$$$$$\   $$$$$$\
+// $$  _____|$$ |$$  __$$\ $$ |  $$ |$$  __$$ |$$ |\_$$  _|  $$  __$$\ $$  __$$\
+// $$ /      $$ |$$ /  $$ |$$ |  $$ |$$ /  $$ |$$ |  $$ |    $$ /  $$ |$$ | \__|
+// $$ |      $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$ |  $$ |$$\ $$ |  $$ |$$ |
+// \$$$$$$\  $$ |\$$$$$   |\$$$$$   |\$$$$$$  |$$ |  \$$$   |\$$$$$   |$$ |
+//  \_______|\__| \______/  \______/  \_______|\__|   \____/  \______/ \__|
+//
+// This file is part of Clouditor Community Edition.
+
+package ccl_test
+
+import (
+	"encoding/json"
+	"testing"
+	"time"
+
+	"clouditor.io/clouditor/ccl"
+	"github.com/stretchr/testify/assert"
+)
+
+func TestRuleFromFile(t *testing.T) {
+	var (
+		err     error
+		success bool
+		j       = `{"field": "value"}`
+		o       map[string]interface{}
+	)
+
+	err = json.Unmarshal([]byte(j), &o)
+
+	assert.Nil(t, err)
+
+	success, err = ccl.RunRuleFromFile("test_rule.ccl", o)
+
+	assert.Nil(t, err)
+	assert.True(t, success)
+}
+
+// just a shortcut
+type m map[string]interface{}
+
+func TestRunRule(t *testing.T) {
+	var err error
+
+	var testData = []struct {
+		name            string
+		json            map[string]interface{}
+		ccl             string
+		expectedError   error
+		expectedSuccess bool
+	}{
+		{
+			name:            "string compare",
+			json:            m{"stringField": 1},
+			ccl:             "Object has stringField == 1",
+			expectedSuccess: true,
+		},
+		{
+			name:            "int compare",
+			json:            m{"intField": 1},
+			ccl:             "Object has intField == 1",
+			expectedSuccess: true,
+		},
+		{
+			name:            "float compare",
+			json:            m{"floatField": 1.5},
+			ccl:             "Object has floatField == 1.5",
+			expectedSuccess: true,
+		},
+		{
+			name:            "bool compare",
+			json:            m{"boolField": false},
+			ccl:             "Object has boolField == false",
+			expectedSuccess: true,
+		},
+		{
+			name:            "not expression",
+			json:            m{"boolField": false},
+			ccl:             "Object has not boolField == false",
+			expectedSuccess: false,
+		},
+		{
+			name:            "isempty expression with nonexisting field",
+			json:            m{},
+			ccl:             "Object has empty field",
+			expectedSuccess: true,
+		},
+		{
+			name:            "isempty expression with empty string",
+			json:            m{"field": ""},
+			ccl:             "Object has empty field",
+			expectedSuccess: true,
+		},
+		{
+			name:            "isempty expression with boolean false",
+			json:            m{"field": false},
+			ccl:             "Object has empty field",
+			expectedSuccess: true,
+		},
+		{
+			name:            "isempty expression with integer zero",
+			json:            m{"field": 0},
+			ccl:             "Object has empty field",
+			expectedSuccess: true,
+		},
+		{
+			name:          "field does not exist",
+			json:          m{},
+			ccl:           "Object has (field == 1)",
+			expectedError: ccl.ErrFieldNameNotFound,
+		},
+		{
+			name:          "field not a map",
+			json:          m{"nested": 1},
+			ccl:           "Object has nested.field == 1",
+			expectedError: ccl.ErrFieldNoMap,
+		},
+		{
+			name:            "nested field",
+			json:            m{"nested": map[string]interface{}{"field": 1}},
+			ccl:             "Object has nested.field == 1",
+			expectedSuccess: true,
+		},
+		{
+			name:          "nested field does not exist",
+			json:          m{"nested": map[string]interface{}{"field": 1}},
+			ccl:           "Object has nested.someotherfield == 1",
+			expectedError: ccl.ErrFieldNameNotFound,
+		},
+		{
+			name:            "not equals operator int",
+			json:            m{"field": 2},
+			ccl:             "Object has field != 1",
+			expectedSuccess: true,
+		},
+		{
+			name:            "not equals operator string",
+			json:            m{"field": "value"},
+			ccl:             "Object has field != \"some_value\"",
+			expectedSuccess: true,
+		},
+		{
+			name:            "not equals operator float",
+			json:            m{"field": 1.5},
+			ccl:             "Object has field != 2",
+			expectedSuccess: true,
+		},
+		{
+			name:            "not equals operator bool",
+			json:            m{"field": false},
+			ccl:             "Object has field != true",
+			expectedSuccess: true,
+		},
+		{
+			name:            "less operator",
+			json:            m{"field": 1},
+			ccl:             "Object has field < 2",
+			expectedSuccess: true,
+		},
+		{
+			name:            "less equals operator",
+			json:            m{"field": 2},
+			ccl:             "Object has field <= 2",
+			expectedSuccess: true,
+		},
+		{
+			name:            "greater operator",
+			json:            m{"field": 2},
+			ccl:             "Object has field > 1",
+			expectedSuccess: true,
+		},
+		{
+			name:            "greater equals operator",
+			json:            m{"field": 1},
+			ccl:             "Object has field >= 1",
+			expectedSuccess: true,
+		},
+		{
+			name:            "contains operator",
+			json:            m{"field": "myvalue"},
+			ccl:             "Object has field contains \"my\"",
+			expectedSuccess: true,
+		},
+		{
+			name:            "within expression",
+			json:            m{"field": 4},
+			ccl:             "Object has field within 1,2,3,4",
+			expectedSuccess: true,
+		},
+		{
+			name:            "time comparison before",
+			json:            m{"field": time.Now().Add(1 * time.Hour * 24)},
+			ccl:             "Object has field before 2 days",
+			expectedSuccess: true,
+		},
+		{
+			name:            "time comparison after",
+			json:            m{"field": time.Now().Add(10 * time.Second)},
+			ccl:             "Object has field after 2 seconds",
+			expectedSuccess: true,
+		},
+		{
+			name:            "time comparison younger",
+			json:            m{"field": time.Now().Add(-2 * time.Hour * 24 * 30)},
+			ccl:             "Object has field younger 3 months",
+			expectedSuccess: true,
+		},
+		{
+			name:            "time comparison older",
+			json:            m{"field": time.Now().Add(-10 * time.Hour * 24 * 30)},
+			ccl:             "Object has field older now",
+			expectedSuccess: true,
+		},
+		{
+			name: "in any expression",
+			json: m{"array": []map[string]interface{}{
+				{"field": 1},
+				{"field": 2},
+			}},
+			ccl:             "Object has field == 1 in any array",
+			expectedSuccess: true,
+		},
+		{
+			name: "in all expression",
+			json: m{"array": []map[string]interface{}{
+				{"field": 1},
+				{"field": 2},
+			}},
+			ccl:             "Object has field == 1 in all array",
+			expectedSuccess: false,
+		},
+		{
+			name:          "syntax error",
+			json:          m{},
+			ccl:           "Object has nonsense",
+			expectedError: ccl.ErrUnexpectedExpression,
+		},
+		{
+			name:          "syntax error in any expression",
+			json:          m{},
+			ccl:           "Object has (field == in any array",
+			expectedError: ccl.ErrUnexpectedExpression,
+		},
+	}
+
+	for _, data := range testData {
+		t.Run(data.name, func(t *testing.T) {
+			success, err := ccl.RunRule(data.ccl, data.json)
+
+			assert.ErrorIs(t, err, data.expectedError, "did not match error")
+			assert.Equal(t, data.expectedSuccess, success, "did not match expected success outcome")
+		})
+	}
+
+	assert.Nil(t, err)
+}
diff --git a/ccl/test_rule.ccl b/ccl/test_rule.ccl
new file mode 100644
index 00000000..2e544313
--- /dev/null
+++ b/ccl/test_rule.ccl
@@ -0,0 +1,1 @@
+Rule has field == "value"
\ No newline at end of file
diff --git a/service/discovery/discovery_test.go b/service/discovery/discovery_test.go
index bb1a7596..656ea8fc 100644
--- a/service/discovery/discovery_test.go
+++ b/service/discovery/discovery_test.go
@@ -49,7 +49,6 @@ func TestQuery(t *testing.T) {
 
 	assert.Nil(t, err)
 	assert.NotNil(t, response)
-
 	assert.NotEmpty(t, response.Result.Values)
 
 	m := response.Result.Values[0].GetStructValue().AsMap()