This repository provides means of compiling a simple "javascript-like" language to json-logic. We started this project to simplify writing of CertLogic rules for the verification of GreenCertificates. Since reading and reviewing json-logic gets quite tedious when the rule complexity increases, something easier to read was needed.
AifC /ɪˈfsiː/ [eff-see] is an acronym explaining the pain of json-logic. Json-logic itself is nice to use, since one can easily evaluate certain logic given a context in a secure sandbox. Though the syntax itself is more similiar to something like an AST. Hence, the layer above json-logic, providing a syntax more like modern programming languages.
There is a project in aifc, which provides a simple compiler backend. It takes an input file and produces the corresponding json file.
The parser uses peg to generate an internal AST from which the resulting json-logic is produced. The grammar rules can be found in here.
To provide easy access to the language, we built a static website, using the rust parser as a web assembly module. The website should run on most modern browsers. It has built-in the monaco editor backend with a basic language definition for aifc.
On the web backend, the resulting rules can also be verified by evaluating them on a an actual JSON data context.
There is a web-backend available for testing.
To provide an easier development cycle, there exists a vscode language definition, providing basic syntax highlighting.
Note: The compiler automatically applies certain
CertLogic
specific desugaring
if (a < b) { a }
JSON-logic
{
"if": [
{
"<": [
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
}
]
}
if (a < b) { a } else { b }
JSON-logic
{
"if": [
{
"<": [
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
},
{
"var": "b"
}
]
}
The switch statement allows easy switching on multiple conditions. Further, one can define sub conditions after the switch case to allow for certain pattern matching capabilities. One should note that providing an array in the case clause, will use the in
opperator, to check if the switch clause is contained in the case clause.
Every switch statement needs a default case!
switch(medical_product) {
["a", "b"] : if dosis_number === 1 => {
true
}
"c" => {
true
}
_ => {
false
}
}
JSON-logic
{
"if": [
{
"and": [
{
"in": [
{
"var": "medical_product"
},
[
"a",
"b"
]
]
},
{
"===": [
{
"var": "dosis_number"
},
1
]
}
]
},
true,
{
"if": [
{
"===": [
{
"var": "medical_product"
},
"c"
]
},
true,
false
]
}
]
}
The following logic operators are supported:
- and / &&
- or / ||
- not / !
a and b
JSON-logic
{
"and": [
{
"var": "a"
},
{
"var": "b"
}
]
}
a or b
JSON-logic
{
"!": [
{
"and": [
{
"!": [
{
"var": "a"
}
]
},
{
"!": [
{
"var": "b"
}
]
}
]
}
]
}
not b
JSON-logic
{
"!": [
{
"var": "b"
}
]
}
When using comparison of time events the following operators must be used to comply with CertLogic
:
- is before
- is not before
- is after
- is not after
a as DateTime is before b as DateTime
JSON-logic
{
"before": [
{
"var": "a"
},
{
"var": "b"
}
]
}
Suffixing a integer literal with #<timeunit>
will automatically desugar to the corresponding CertLogic
plusTime
logic, if applied to a date
. Note since time operations are only supported on time variables, an as DateTime
cast is needed.
Note that all the time operations are not well defined on weather what a month is. The web-backend uses
chrono
as the time library and hence all operations are lowered to the correspondingchrono
functions.
- hour/hours
- day/days
- month/months
- year/years
a as DateTime + 1#day
JSON-logic
{
"plusTime": [
{
"var": "a"
},
1,
"day"
]
}
Further, strings conforming to a date or date time are automatically considered as date variables
:
"2021-01-01"
JSON-logic
{
"plusTime": [
"2021-01-01",
0,
"day"
]
}
To be able to use the date-string in a time-operation, a cast to string is needed, as CertLogic can only operate on strings:
("2020-01-01" as String) + 1#day
JSON-logic
{
"plusTime": [
"2021-01-01",
1,
"day"
]
}
For time evaluation the shorthand now()
exists, which desugars to a variable from the context.
now()
JSON-logic
{
"var": "external.validationClock"
}
As CertLogic
only provides a subset of truthy/falsy
values we added the Boolean
cast to ensure something is interpreted as a boolean.
a as Boolean
JSON-logic
{
"!": [
{
"!": [
{
"var": "a"
}
]
}
]
}
For the min
and max
expression desugarings are available:
min(a,b,c,d)
JSON-logic
{
"if": [
{
"<": [
{
"if": [
{
"<": [
{
"if": [
{
"<": [
{
"if": [
{
"<": [
{
"if": [
{
"<": [
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
}
]
},
{
"if": [
{
"<": [
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
}
]
},
{
"var": "b"
}
]
},
{
"if": [
{
"<": [
{
"if": [
{
"<": [
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
}
]
},
{
"if": [
{
"<": [
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
}
]
},
{
"var": "b"
}
]
},
{
"var": "c"
}
]
},
{
"if": [
{
"<": [
{
"if": [
{
"<": [
{
"if": [
{
"<": [
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
}
]
},
{
"if": [
{
"<": [
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
}
]
},
{
"var": "b"
}
]
},
{
"if": [
{
"<": [
{
"if": [
{
"<": [
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
}
]
},
{
"if": [
{
"<": [
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
}
]
},
{
"var": "b"
}
]
},
{
"var": "c"
}
]
},
{
"var": "d"
}
]
},
{
"if": [
{
"<": [
{
"if": [
{
"<": [
{
"if": [
{
"<": [
{
"if": [
{
"<": [
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
}
]
},
{
"if": [
{
"<": [
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
}
]
},
{
"var": "b"
}
]
},
{
"if": [
{
"<": [
{
"if": [
{
"<": [
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
}
]
},
{
"if": [
{
"<": [
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
}
]
},
{
"var": "b"
}
]
},
{
"var": "c"
}
]
},
{
"if": [
{
"<": [
{
"if": [
{
"<": [
{
"if": [
{
"<": [
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
}
]
},
{
"if": [
{
"<": [
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
}
]
},
{
"var": "b"
}
]
},
{
"if": [
{
"<": [
{
"if": [
{
"<": [
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
}
]
},
{
"if": [
{
"<": [
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
}
]
},
{
"var": "b"
}
]
},
{
"var": "c"
}
]
},
{
"var": "d"
}
]
}
max(a,b)
JSON-logic
{
"if": [
{
">": [
{
"var": "a"
},
{
"var": "b"
}
]
},
{
"var": "a"
},
{
"var": "b"
}
]
}
There are only multiline comments supported, using the following syntax
/* This is a single line */
/*
This is a multi line...
...comment
*/
Currently, basic variable statements are supported. Anything which either evaluates to a previously defined variable or an expression can be used as an rvalue. The compiler will report variables which are defined twice.
Variables are scoped to the corresponding block, inheriting the parent scope. Consider the following examples:
let a = b;
let c = a;
c
will produce
{
"var": "b"
}
The following will fail, since a
is defined twice:
let a = "test";
let a = 1;
This on the other hand is fine, since a
is scoped to either the if
or else
branch.
if (true) {
let a = "true";
a
} else {
let a = "false";
a
}
The compiler will allow import
statements to include other files. First all input is stiched together and then the compiler tries to parse it. As such, only imports which define variables via let
binding are supported. For obvious reasons, the online web compiler does not support import statements. Imported files must be referenced relative to the file being compiled.
globals.aifc
let yes = true;
let no = false;
test.aifc
/* globals.aifc is in the same directory as test.aifc */
import "globals.aifc";
if (yes) {
true
} else {
false
}