VivJson's quick reference

Table of contents

Overview

VivJson is extension of JSON.

The following 2 blocks are equivalent and valid statements in VivJson.
The base concept is that the block {...} is function. After evaluating it, it will be key-value pairs (a.k.a. map, dict, hashes, associative arrays) of host language. Finally, it can be exported as JSON's object.

{
    "a": 100,
    "b": 15,
    "data": 115
}
{
    a = 100
    b = 15
    data = 115
}

Codes that are written with VivJson is suitable to pack together with data.
In the following sample, codes can be evaluated after extracting from data.

{
    "name": "foo",
    "number": 3,
    "code": "max = 10, if (number > max) {number = max}"
}

On the other hand, in the following sample, it is assumed that itself is evaluated as VivJson. And it is assumed that _max is given from caller. The result becomes {"name": "foo", "number": 2} if _max is 2.

{
    "name": "foo",
    "number": 3,
    if (number > _max) {
        number = _max
    }
}

Execute

In command-line

Print "Hello".

$ python3 -m vivjson 'print("Hello")'
Hello
{}

However "{}" is redundant. It can be reduced with return("").

$ python3 -m vivjson 'print("Hello"), return("")'
Hello

Otherwise, return is used instead of print.

$ python3 -m vivjson 'return("Hello")'
Hello

Arguments can be given before statement.

$ python3 -m vivjson "a:3,b:7,return(a+b)"
10
$ python3 -m vivjson "{a:3,b:7}" "return(a+b)"
10
$ python3 -m vivjson "x=" + "{a:3,b:7}" "return(x.a+x.b)"
10
$ python3 -m vivjson "3,7" "return(_[0]+_[1])"
10
$ python3 -m vivjson 3 7 "return(_[0]+_[1])"
10
$ python3 -m vivjson 10 "return(_)"
10

File can be given instead of text.
File extension must be ".viv" or ".json".

$ python3 -m vivjson test.viv
$ python3 -m vivjson data.json calc.viv
$ python3 -m vivjson "{a:3,b:7}" calc.viv

PIPE can be used with "-i" option.

$ echo "return(3*2)" | python3 -m vivjson -i
$ echo "a=3" | python3 -m vivjson -i "return(a*2)"
$ echo '{"a":3}' | python3 -m vivjson -i "return(a*2)"
$ cat test.viv | python3 -m vivjson -i
$ cat data.json | python3 -m vivjson -i "return(a*b)"
$ echo '[{"name": "dog", "number": 2}, {"name": "cat", "number": 3}]' | python3 -m vivjson -i=values "result = {}, for(value in values){result[value.name] = value.number}, return(result)"
{"dog": 2, "cat": 3}

In Python's code

Parse and execution are done as below.
run function accepts multi arguments. File paths are also accepted.
The returned value is Python's native value.
There is no exception. When error occurs, error message is stored into the returned value.

from vivjson.viv import Viv
value, error_message = Viv.run('a: 3, b: 2, return(a / b)')
print(value)  # 1.5
from vivjson.viv import Viv
value, error_message = Viv.run('a: 3, b: 0, return(a / b)')
print(error_message)  # [Viv] Error: Cannot evaluate "(a / b)" in (line: 1, column: 20)

Representation of several value is different between Python and VivJson.
However it is converted automatically.
The returned value of run is represented as Python's value.

Python VivJson
True true
False false
None null
tuple (N/A)

Although key of Python's dict can be number, VivJson can't treat it.

In Java's code

Parse and execution are done as below.
Each function accepts multi arguments. File paths are also accepted.
The returned value is Java's object or primitive value.

There is two types based on managing error.
1st type does not throw exception. Instead of it, the returned value indicates error if error occurs.
2nd type throw exception if error occurs. And the returned value has only the actual returned value.
The suffix of 2nd type's method name is "Ex".

import com.benesult.vivjson.Viv;
Float value = Viv.getFloat("a: 3, b: 2, return(a / b)");
System.out.println(value);  // 1.5
import com.benesult.vivjson.Viv;
String code = "a: 3, b: 0, return(a / b)";
Float value1 = Viv.getFloat(code);  // null
try {
    float value2 = Viv.getFloatEx(code);
} catch (VivException e) {
    System.err.println(e.getMessage());  // [Viv] Error: Cannot evaluate "(a / b)" in (line: 1, column: 20)
}

JSON offers flexible data structure. Its manipulation is so easy in dynamically typed language. On the other hand, in statically typed language, such as Java, it is so difficult.
Thus, this embedded script empowers to manipulate JSON in Java.

print("Hello")
print("1", 2, true)

Comment

There are 3 styles.

year = 2024  # The past
month = 1  // It is valid as month.
day = 30  /* It is valid except
           * February.
           */
set_person(/* name */ "tom", /* age */ 20)

Assignment

There is no declaration like "let" and "var".

a = 100
b = "xyz"
c = true
d = null
e = [1, 2, 3]
f = {"name": "foo", "number": 25}

: can be used instead of =.
Their difference is scope.
: creates the local variable.

Even if the outer block's variable is modified, it is not contained as the inner block's member. For example, the following code makes that the value of x is {"a": 300, "c": {"b": 300}}. In other words, the created variable is contained as its enveloped block's member.

x = {
    a = 100
    c = {
        a = a + 200
        b = a
    }
}

When c needs a as member, it is possible with : instead of =. : creates the local variable.
In the following sample, a: a + 200 is the outer block's a + 200 because the creation of a is done after calculating a + 200. Then, in b = a, a of the local variable is used. As a result, the value of x is {"a": 100, "c": {"a": 300, "b": 300}}.

x = {
    a = 100
    c = {
        a: a + 200
        b = a
    }
}

The variable of the left-hand side can be enclosed with quotation marks. So {"a": 100} and {a = 100} is equivalent.

"x": {"a": 100}
y = {a = 100}
if (x == y) {print("same")}  # same

By the way, the variable is removed with remove.

a = 100
remove(a)
print(a)  # null

When the undefined variable is read, its value is null.
Its existence is obtained with in operator.

x = {a: 10, b: 20}

print("a" in x)  # true
print(x)  # {"a": 10, "b": 20}
print(x.a)  # 10

remove(x.a)

print("a" in x)  # false
print(x)'  # {"b": 20}
print(x.a)  # null

Number

It is allowed only decimal.

There are two types.

Integer and Floating-point number is converted automatically.

For example, 1, 1.5, -10, 3.7e+5

Arithmetic operation and Comparison are possible.

Increment and decrement is realized with assignment operator: i += 1, i -= 1

"int" and "float" are converted with int function and float function, such as a = int(1.5) # 1 and a = float(1) # 1.0.

It can be converted into string with string function, such as a = string(3) # "3".

type function returns "int" or "float".

String

String is allowed only UTF-8.

The quotation mark is either of ' or ". For example, 'This is text.', "Sample"
The length is given with len function. For example, len("Sample")

Concatenation is done with + or * operator.

Removing word is done with - operator: a = "large-dog&small-dog&2cat" - "dog" # "large-&small-&2cat"

Splitting is done with / operator: a = "x,y,z" / "," # ["x", "y", "z"]

== and != are used to judge whether left-hand side string and right-hand side one are same or not.

a = "xyz"
b = a == "xyz"  # true
c = a != "xyz"  # false

in is used to judge whether left-hand side string is contained in right-hand side array/string or not.

a = "dog" in ["cat", "dog", "bird"]  # true
a = "pig" in ["cat", "dog", "bird"]  # false
a = "dog" in "cat&dog&bird"  # true

String's number is converted into pure number with int or float function, such as a = int("2"), a = float("3.5").

type function returns "string". For example, a = type("xyz") # "string"

Boolean

There are two values: true, false

Logical operation are possible.

a = 100
print(a == 100)  # true

b = "xyz"
print(b != "xyz")  # false
print(not (b != "xyz"))  # true

if (a == 100 and b == "xyz") {
    print("Both are satisfied.")
}
if (type(a) == "string" or type(b) == "string") {
    print("Either type is string.")
}

In logical operation, null, 0, 0.0 is treated as false. The other is treated as true.

and and or make Short-circuit evaluation (minimal evaluation). For example, x = false and foo() and x = true or foo() don't execute foo().

type function returns "boolean". For example, a = type(false) # "boolean"

null

When the undefined variable is read, its value is null.

a = [1, 2, 3]
b = [a[2], a[3]]  # [3, null]

type function returns "null". For example, a = type(null) # "null"

Array

Array is initialized with assignment.

a = []
a = [10, 20]
a = [100] * 5  # [100, 100, 100, 100, 100]

It can contain various type's element together.

a = [1, true, {"a": null}, "x"]

Its specific element is accessed with index.
The index starts from 0.
The size (length) is given with len function.
Backward access is possible with the negative index.
The redundant last delimiter is permitted.

a = [10, 20, 30, 40, 50]
b = a[0]  # 10
c = len(a)  # 5
d = a[-2]  # 40
e = [1, 2, 3, ]  # [1, 2, 3]
a[0] = -100  # a = [-100, 20, 30, 40, 50]

Syntax sugar is existed. a.2 is equivalent to a[2]. Similarly, a.-1 is equivalent to a[-1].

a = [10, 20, 30, 40, 50]
b = a.2  # 30
c = a.-1  # 50
a.0 = -100  # a = [-100, 20, 30, 40, 50]

,, ;, white-space, and line-break (new line code) can be used as delimiter.

a = [1, 2, 3]
b = [1; 2; 3]
c = [1 2 3]
d = [
    1
    2
    3
]
e = [1
     2
     3]

Appending element is done with + operator.

Removing element is done with - operator or remove.

There is two roles about in operator.

type function returns "array". For example, x = type([3, "a", "b"]) # "array"

Block

The role of Block:

Any block is equivalent to the function.
So its result can be assigned into a variable of left-hand side. In other words, assignment of the block is immediately invoked function expression (IIFE).
Basically, it becomes key-value pairs (a.k.a. map, dict, hashes, associative arrays).
When you want to return other value, it is possible with return or := statement. However return 10 is not allowed. return statement needs parentheses () for the returned value though it isn't needed in most of the language.
When there are return and := together and return has value, return's value is returned.

function sample() {
    a = 3
    b = 2
}

x = sample()  # {"a": 3, "b": 2}


function sample() {
    a = 3
    b = 2
    return(10)
}

x = sample()  # 10


function sample() {
    a = 3
    return
    b = 2
}

x = sample()  # {"a": 3}


function sample() {
    a = 3
    := 10
    b = 2
    print("Not interrupt")
}

x = sample()  # 10


x = if (true) {a = 3} else {a = 0}  # {"a": 3}

x = if (true) { := 3} else { := 0}  # 3

Note that same name's function can be defined again.

The member is accessed with its name.
The size (length) is given with len function.
The redundant last delimiter is permitted.

w = {"a": 3, "b": 100}
x = w["b"]  # 100
y = len(w)  # 2
z = {"a": 3, "b": 100,}  # {"a": 3, "b": 100}
w["b"] = 20  # w = {"a": 3, "b": 20}

Syntax sugar is existed. w.b is equivalent to w["b"].

w = {"a": 3, "b": 100}
x = w.b  # 100
w.b = 20  # w = {"a": 3, "b": 20}

When member's name is number (of string), its quotation can be omitted as syntax sugar.

w = {"a": 3, "b": 100, "5": 7}
x = w.5  # 7
y = w[5]  # 7
w.5 = 1000  # w = {"a": 3, "b": 100, "5": 1000}
w.-1 = true  # w = {"a": 3, "b": 100, "5": 1000, "-1": true}

Arithmetic operation is possible for each member.

Removing element is done with - operator or remove.

There is two roles about in operator.

The right hand sided . of in represents this block.

type function returns "block". For example, x = type({"dog": 2, "cat": 2}) # "block"

Function

Function's definition begins from modifier "function".
Function call is Call by value.

function x2(k) {
    k *= 2
    print(k)
}

a = 3
x2(a)  # 6 is printed.
print(a)  # 3 is printed.


function x2(k) {
    k[0] *= 2
    k[1] *= 2
    print(k)
}

a = [3, 5]
x2(a)  # [6, 10] is printed.
print(a)  # [3, 5] is printed.

Note that same name's function can be defined again.

Array and block (key-value pairs) can be Call by reference using "reference".

function x2(reference k) {
    k[0] *= 2
    k[1] *= 2
    print(k)
}

a = [3, 5]
x2(a)  # [6, 10] is printed.
print(a)  # [6, 10] is printed.

Even if number of argument and number of parameter is different, operation is done.
In other words, error is not happen for this difference.

function add(a, b) {
    return(a + b)
}

print(add(3, 2, 1))  # 5 --- 3rd argument is ignored in function "add".

Placement of statement

The explicit statement terminator and delimiter are not needed.
Therefore, the following code is valid.

sum = 0 for (item in [1 2 3 4 5]) {sum += item} return(sum)  # 15

,, ;, white-space, and line-break (new line code) can be used as statement terminator and delimiter.
So the following code is valid too.

sum = 0, for (item in [1, 2, 3, 4, 5]) {sum += item}; return(sum)  # 15

It is impossible that value (number, string, boolean, null), array, block, Arithmetic operation, Logical operation, and Comparison are placed directly.

100               # Error
"invalid"         # Error
true              # Error
null              # Error
[1, 2, 3]         # Error
{"x": 1, "y": 2}  # Error
15 / 3            # Error
false and true    # Error
10 < 100          # Error

They are used as operand and argument.

foo = 3
function test() {print("test")}
test()
for (i = 1; i < 10; i += 1) {foo += 1 continue foo -= 1}
while (foo > 0) {foo -= 1 break}
do {foo = 100}
if (foo < 0) {print("-")} elseif (foo == 0) {print("0")} else {print("+")}

Variable/Function name

b = {
    _temp = 3
    a = _temp + 2
}
print(b)  # {"a": 5}

When you need number as key (name of member), it is realized as below. However it must be string.

a = {"0": 100}
return(a["0"])  # 100
return(a[0])  # 100 -- a[0] is syntax sugar of a["0"].
return(a.0)  # 100 -- a.0 is syntax sugar of a["0"]

a = {}
a[0] = 100  # a[0] is syntax sugar of a["0"]
a.1 = 200  # a.1 is syntax sugar of a["1"]
return(a)  # {"0": 100, "1": 200}

In the same way, the letter that is not allowed as name can be used as key.

a = {}
a["!."] = "test"
return(a)  # {"!.": "test"}

Loop

There are 3 types as Loop: for, while, and do
They accept break/continue.

Loop times are limited with configuration. The default maximum loop times are 1000.