VivJson's specification Ver.1.0.0

Features

Tiny language for spending low resource and embedding within App, etc.

Codes that are written with VivJson is suitable to pack together with data.

Table of contents

Value

They are equivalent to JSON basically.

Number

Number is allowed only decimal.

There are two types.

Integer and Floating-point number is converted automatically.
Note that String and Number is not converted automatically. string, int, and float functions should be called.

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

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")

Boolean

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

null

When the undefined variable is read, its value is null.
For example, a value of the following b is [3, null].

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

Array

Array has elements that are surrounded with square brackets. For example, [1, 2, 3]
Array can contain various type's element together. For example, [1, true, {"a": null}, "x"]
Its specific element is accessed with index. For example, a[2]
The size (length) is given with len function. For example, len(a)
The index starts from 0. And the index ends to (len(a) - 1) for Array a.
Backward access is possible with the negative index. For example, a[-1] is 3 for a = [1, 2, 3].
Syntax sugar: a.2 is equivalent to a[2]. Similarly, a.-1 is equivalent to a[-1].


,, ;, white-space, and line-break (new line code) can be used as delimiter.
So the following arrays are equivalent.

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

By the way, the redundant last delimiter is permitted.
So f = [1, 2, 3, ] is also equivalent to the above.

Block and Returned value

Block has statements that are surrounded with curly brackets. For example, {i = 0, print(i)}.
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. object, map, dict, hashes, associative arrays).
When you want to return other value, it is possible with return or := statement.

For example, the following assignment makes a value {"a": 100, "b": 15} and it is assigned into variable x.

x = {
    a = 100
    b = 5
    b *= 3
}

if/elseif/else statement can be assigned too. The following if assigns {"y": 1} into variable x.

x = if (true) { y = 1 } else { y = 2 }

for/while/do loop can be assigned too. The following for assigns {"i": 10, "j": 18} into variable x.

x = for (i = 0; i < 10; i += 1) { j = i * 2 }

When you need only j, i should be replaced with _i. As a result, {"j": 18} is assigned into variable x

x = for (_i = 0; _i < 10; _i += 1) { j = _i * 2 }

Furthermore, := statement assigns the plain value 18 into variable x

x = for (i = 0; i < 10; i += 1) { := i * 2 }

In pure function or anonymous function (includes IIFE), return statement can be used instead of := statement.
The following variable x's value becomes 3.

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

x = sum(1, 2)

When return is not existed/evaluated, the returned value is only variables that are created in this block.
Thus, the parameter (argument) is not contained.
So the following variable x's value becomes {"c": 3}.

function sum(a, b) {
    c = a + b
}

x = sum(1, 2)

The independent block is not allowed. For example, the following code is invalid.

a = 100
{
    b = 200
}

On the other hand, the following code is valid. There are 2 reasons. Firstly, it is argument. Secondly, line-break (new line code) can be used instead of ; and ,.

function sample(x, y) {
    print(x + y.b)
}

#        It is valid.
#        |
#        V
sample(
    100
    {
        b = 200
    }
)

The explicit statement terminator is not needed.
However it can specify with ;, ,, or line-break (new line code).
Thus, {a = 100 b = 5}, {a = 100; b = 5;}, and {a = 100, b = 5} are equivalent.

: can be used instead of = for the assignment.
(Note that the variable of the left-hand side is treated as local variable when : is used.)
The variable of the left-hand side can be enclosed with quotation marks.

As a result, JSON's object is valid statements for VivJson.

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

y = {
    a = 100
    b = 15
    data = 115
}

if (x == y) {print('same')}

There are various styles in order to access member.
For example, x = {"a": 100, "b": [true, {"c": 3, "0": "foo"}]}:

Function

Any block {...} is equivalent to the function.
There are 6 types as the function.

  1. Anonymous function
  2. Pure function: It is well known type, such as function test(a) {return(a * 2)}.
  3. Loop: for, while, and do.
  4. Limited function: if, elseif, and else.
  5. Closure
  6. Class constructor/method

Anonymous function

There are 3 situations.

  1. Immediately invoked function expression (IIFE).
  2. Argument of the block.
  3. Main code.

Anonymous function: Immediately invoked function expression (IIFE)

Assignment of the block is immediately invoked function expression (IIFE).
For example, after executing code, the value of x is {"a": 3, "b": 5} and 15 is printed.

x = {
    a = 3
    b = a + 2
    print(a * b)
}

Anonymous function: Argument of the block

Argument of the block is anonymous function if "function" is given as modifier of the corresponded parameter.

function run(function worker) {  # <-- The modifier of
    worker()                     #     parameter "worker"
}                                #     is "function".

run({
    print("test")
})

A block of the last argument can be moved after parentheses () as below.

run() {
    print("test")
}

When other argument is nothing, parentheses () can be omitted as below.

run {
    print("test")
}

On the other hand, when modifier of the corresponded parameter is omitted, the block is evaluated and its result is given as argument.
Thus the following code and the above code make same result.

function output(value) {
    print(value)
}

output({
    return("test")
})

Anonymous function: Main code

In command-line, for example, {print(10)} in python3 -m vivjson '{print(10)}' is main code.
Since the outermost bracket can be omitted as syntax-sugar, print(10) in python3 -m vivjson 'print(10)' is also main code.

In file, the following codes are main code.
The outermost bracket can be omitted as syntax-sugar.

{
    a = 100
    b = 5
    b *= 3
    function add(x, y) {
        return(x + y)
    }
    data = add(a, b)
}
a = 100
b = 5
b *= 3
function add(x, y) {
    return(x + y)
}
data = add(a, b)
a = 100, b = 5, b *= 3, function add(x, y) {return(x + y)}, data = add(a, b)

Pure function

Pure function is well known type, such as function test(a) {return(a * 2)}.
Its definition begins from modifier "function".

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

The above function returns {"a": 3, "b": 2}.
When you want to return another value, such as literal, it is realized with return statement. For example, the following function returns 10.

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

return 10 is not allowed. return statement needs parentheses () for the returned value though it isn't needed in most of the language.
On the other hand, return (that excludes parentheses and value) is allowed too. The following function returns {"a": 3, "b": 2}. a is not 5 because this function is escaped with return statement before a = a + b.

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

:= also returns the returned value.
However := does not interrupt the function. So the following function executes print.

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

x = sample()  # 10

When there are return and := together and return has value, return's value is returned.

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

x = sample()  # 5
return := The returned value
nothing nothing Block's original value
nothing := value Value of :=
return nothing Block's original value
return := value Value of :=
return(value) nothing Value of return
return(value) := value Value of return

Loop

In VivJson, for, while, and do are implemented as special function.

for

while

The following while loop repeats 10 times.

i = 0
while (i < 10) {
      :
      :
    i += 1
}

The following while loop is infinite.
However loop times are limited with configuration. The default maximum loop times are 1000.

while (true) {
    :
    :
}

while loop can also make the returned value just like for loop.

do

This is not do { ... } while ( condition ).
This block is executed only one time. However it can be executed more times with continue.

This is useful as below.

x = 1000
y = null
do {
    y = "1xx"
    if (x >= 100 and x < 1000) {
        break
    }
    y += "x"
    if (x >= 1000 and x < 10000) {
        break
    }
    y = "other"
}
print(y)  # "1xxx"

do loop can also make the returned value just like for loop.

Limited function (if/elseif/else)

In VivJson, if, elseif, and else are implemented as special function.

if (...) {
    :
    :
} elseif (...) {
    :
    :
} elseif (...) {
    :
    :
} else (...) {
    :
    :
}

if/elseif/else can also make the returned value just like for loop.

Closure

  1. The certain function is defined in other function.
    The former is so called Closure. And the later is so called Enclosure.
  2. Enclosure's returned value is Closure's name.
  3. Variable's value of Enclosure are re-used when Closure is executed.
    In other words, the above values are saved when Closure's name is returned. They are re-used when Closure is executed.
function enclosure(a) {
    x = a
    function closure(y) {
        return(x + y)
    }
    return(closure)
}

z1 = enclosure(100)
z2 = enclosure(200)
a = z1(5)
b = z2(10)
print(a, b)  # 105, 210

Class constructor/method

When other function is finished, key-value pairs are collected from its block.
However variables whose prefix is "_" and the definition of function are removed.

On the other hand, class constructor keeps whole variables/definitions.

To tell the truth, class and block of anonymous function are same structure. When anonymous function is executed as class constructor, its Closure is used as class method.

Main code can be used as class constructor a present.

break/continue/return

Any block is equivalent to the function as mentioned above.
It is classified into 6 types. One of the reason is that break/continue/return works as the expected behavior.

  1. Anonymous function: Immediately invoked function expression (IIFE), Argument of the block, Main code
  2. Pure function: It is well known type, such as function test(a) {return(a * 2)}.
  3. Loop: for, while, and do.
  4. Limited function: if, elseif, and else.
  5. Closure
  6. Class constructor/method
Type Within Loop Using break/continue Using return In the following diagram
Anonymous function ----- Disable Enable for itself * 1 *, * 2 *, * 7 *
Pure function ----- Disable Enable for itself * 6 *
Loop ----- Enable for itself Enable for innermost Anonymous/Pure * 4 *, * 9 *
Limited function No Disable Enable for innermost Anonymous/Pure * 3 *, * 8 *
Limited function Yes Enable for innermost loop Enable for innermost Anonymous/Pure * 5 *, * 10 *
* 1 *

a = {
    * 2 *
}

b = 100
if (b >= 100) {
    * 3 *
}

for (i = 0; i < 100; i += 1) {
    * 4 *

    if (i == 0) {
        * 5 *
    }
}

function sample(x) {
    * 6 *

    y = {
        * 7 *
    }

    if (x >= 100) {
        * 8 *
    }

    for (i = 0; i < 100; i += 1) {
        * 9 *

        if (i == 0) {
            * 10 *
        }
    }
}

return accepts the returned value that is surrounded with parentheses (). For example, return(100).
The returned value and parentheses () can be omitted like return. Then the default value is returned.
Although it looks like obsolete C style, VivJson needs this style.

The above default value is the evaluated block. For example, x = { a = 3, b = 2, b *= 5 } is evaluated as x = {"a": 3, "b": 10}.

When break/continue/return is executed, the interrupted block(function)'s result is collected until interrupted location. For example, function test() {a = 10; return; a = 20}, x = test() is evaluated as {"x": {"a": 10}}.

Variable/Function name

The following code is evaluated as b = {"a": 5} because the first letter of "_temp" is "_" (underscore).

b = {
    _temp = 3
    a = _temp + 2
}

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"}

Variable

Scope

Lexical scope is applied.

The inner block (as function) can use its outer block's variable/function.

{
    _factor = 2
    function twice(x) {
        result = x * _factor  # The outer variable "_factor" can be used.
        return(result)
    }
    a = 100
    b = {
        c = a + 200  # The outer variable "a" can be used.
    }
    d = twice(b.c)  # The following variable "e" can't be used here.
                    # Because it is not assigned now.
    e = 5
}

The evaluated block becomes key-value pairs (a.k.a. map, dict, hashes, associative arrays). In the above example, b = {c = a + 200} makes that the value of b is {"c": 300}.

Carefully, 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
    }
}

Undefined variable and Removing

Getting value of the undefined variable is permitted. Its value is null.
On the other hand, setting value may be made error.

a = [1, 2, 3]
print(a[3])  # null though a[3] is out of range.

a[2] = 0  # No problem. It is formal as modification.

a[3] = 4  # <-- Error happens. The cause is out of range.
          #     a += 4 is correct for this purpose (appending).
print(c)  # null though c is undefined.
c = 4  # No problem. It is formal as setting.

Removing the defined variable is possible with remove function.

c = 4
print(c)  # 4
remove(c)
print(c)  # null
a = [1, 2, 3]
print(a)  # [1, 2, 3]
remove(a[1])
print(a)  # [1, 3]

Implicit variable

There is implicit variable _ in the function.
Arguments are stored into _ as array.
For example, when add(3, 2) is called, _[0] is 3 and _[1] is 2.

function add() {
    return(_[0] + _[1])
}

print(add(3, 2))  # 5

Note that there is no implicit variable for immediately invoked function expression (IIFE).

Alias

The parameter of function is alias of implicit variable.
1st parameter is alias of _[0].
2nd parameter is alias of _[1].
:
:

For example, the argument 5 is stored into implicit variable _[0]. Then a *= 2 makes _[0] = 10 because the parameter a is alias of _[0].

function x2(a) {
    a *= 2
    return(_[0])
}

print(x2(5))  # 10

Function parameter and argument

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.

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".
function add_dirty(a, b) {
    return(a + b + _[2])  # Even if 3rd parameter (alias) is not given,
                          # it can be read via implicit variable.
}

print(add_dirty(3, 2, 1))  # 6
function add_whole() {
    sum = 0
    for (value in _) {
        sum += value
    }
    return(sum)
}

print(add_whole(-5, 10, 1.5))  # 6.5
function test(a, b) {
    print(a, b)
}

test(100)  # 100, null --- The missing argument is treated as null.

The explicit statement terminator is not needed.
Thus, line-break (new line code) can be used to separate parameters/arguments.
For example, in the following code, 300 is printed.

function sample(x, y) {
    print(x + y.b)
}

sample(
    100
    {
        b = 200
    }
)

Similarly, the following code has also same arguments.

function sample(x, y) {
    print(x + y.b)
}

sample(100
    {b = 200})

Order of evaluation

Each statement is evaluated step by step from top to bottom and from left to right.
However a part of evaluation is from right to left. Refer to the following table in Order of operations.

When the definition of certain function is not evaluated yet, calling it makes error.

x = test(1)  # Error
function test(a) {
    return(a * 100)
}

On the other hand, the definition of same function name can be written many times.

function test(a) {
    return(a*100)
}

print(test(1))  # 100

function test(a) {
    return(a+10)
}

print(test(1))  # 11

Operations

Order of operations

In the following table, order indicates from highest to lowest.

Operators Associates Purpose
. [] () Left to Right Member/Array access, Function call
+ - not Right to left Unary operation ("not" is logical)
* / % Left to Right Arithmetic
+ - Left to Right Arithmetic
< <= > >= N/A Comparison
== != N/A Comparison
and Left to Right Logical AND
or Left to Right Logical OR
= : += -= *= /= %= := Right to left Assignment

There is no bitwise operation.

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

Arithmetic operation

Note that VivJson needs not only arithmetic operation but also assignment or function call.
Thus, it is invalid that 3 + 2 is existed just under main code. On the other hand, a = 3 + 2 and test(3 + 2) are valid.

Add (+ operator)

left \ right block array string int float boolean null
block block array ERROR ERROR ERROR ERROR block
array array array array array array array array
string ERROR array string string string string string
int ERROR array string int float boolean int
float ERROR array string float float boolean float
boolean ERROR array string boolean boolean boolean boolean
null block array string int float boolean null

Subtract (- operator)

left \ right block array string int float boolean null
block block # block ERROR ERROR ERROR block
array array array array array array array array
string ERROR * string ERROR ERROR ERROR string
int ERROR ERROR ERROR int float ERROR int
float ERROR ERROR ERROR float float ERROR float
boolean ERROR ERROR ERROR ERROR ERROR ERROR boolean
null ERROR ERROR ERROR ERROR ERROR ERROR null

# is block or ERROR
* is string or ERROR

Multiply (* operator)

left \ right block array string int float boolean null
block block ERROR ERROR ERROR ERROR ERROR null
array ERROR ERROR string array array ERROR null
string ERROR string ERROR string string ERROR null
int ERROR array string int float ERROR null
float ERROR array string float float ERROR null
boolean ERROR ERROR ERROR ERROR ERROR ERROR null
null null null null null null null null

Divide (/ operator)

left \ right block array string int float boolean null
block # ERROR ERROR ERROR ERROR ERROR ERROR
array ERROR ERROR ERROR ERROR ERROR ERROR ERROR
string ERROR ERROR array ERROR ERROR ERROR ERROR
int ERROR ERROR ERROR * * ERROR ERROR
float ERROR ERROR ERROR * * ERROR ERROR
boolean ERROR ERROR ERROR ERROR ERROR ERROR ERROR
null null null null x x null ERROR

# is block or ERROR
* is int or float or ERROR
x is null or ERROR

Modulo (% operator)

left \ right block array string int float boolean null
block # ERROR ERROR ERROR ERROR ERROR ERROR
array ERROR ERROR ERROR ERROR ERROR ERROR ERROR
string ERROR ERROR ERROR ERROR ERROR ERROR ERROR
int ERROR ERROR ERROR * * ERROR ERROR
float ERROR ERROR ERROR * * ERROR ERROR
boolean ERROR ERROR ERROR ERROR ERROR ERROR ERROR
null null null null x x null ERROR

# is block or ERROR
* is int or float or ERROR
x is null or ERROR

Logical operation

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

Comparison

Operator Description Operand
== Equal Any
!= Not equal Any
< Less than Number
<= Less than or equal to Number
> Greater than Number
>= Greater than or equal to Number
in Contain Left-hand side: Any,
Right-hand side: block, array, string, or .

. of right-hand side represents this block (scope). For example, foo = 3, bar = 2, return("foo" in .) returns true.

Note that in of for (* in *) {...} makes iterator. It is not comparison. Refer to for loop.

Assignment (Substitute)

Operator Description
= Normal assignment
: Local assignment (Creating local variable)
+= Assignment by addition
-= Assignment by difference
*= Assignment by product
/= Assignment by quotient
%= Assignment by remainder
:= Returned value's assignment (Refer to Block and Returned value)

Placement of statement

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

sum = 0 for (item in [1, 2, 3]) {sum += item} return(sum)

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

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

The following statement can be placed just under block (includes Main code).

{
    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("+")}
}

It is impossible that value (number, string, boolean, null), array, block, Arithmetic operation, Logical operation, and Comparison are placed just under block (includes Main code).

{
    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
}

However it is possible that they are placed as below.

Reserved word

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)

Standard library

Note that remove is not belonged to Standard library. Because it is the statement though it looks like function.