You can write programs in JSON.
This may sound like something else, but I have created a programming language/interpreter for writing programs in JSON format.
The name is “ JSOP,” an abbreviation for JSON Processor, and I named it after LISP (LIST Processor).
https://github.com/JunNishimura/JSOP
This article is not much more than “I made a programming language,” but I hope to explain my motivation and some simple language specifications. I hope you will enjoy the idea that such a language is possible.
{
"command": {
"symbol": "print",
"args": "Hello, World!"
}
}
🔥 Motivation
When I was studying LISP, I came across the statement that the fact that “data and code are identical” is what makes LISP unique.
I thought that if I could create programs in JSON (JavaScript object representation), which is a data structure other than lists, I could create a language like LISP in which “data and code are the same”.
💾 Installation
Homebrew Tap
brew install JunNishimura/tap/JSOP
go intall
go install github.com/JunNishimura/jsop@latest
GitHub Releases
Download exec files from GitHub Releases.
💾 How to use
Since REPL is not provided, you can write your program in any file and pass the file path as a command line argument to execute the program.
jsop ./path/to/file.jsop.json
📖 Language Specification
- Everything is an expression.
- Only
.jsop
and.jsop.json
are accepted as file extensions.
Integer
Integer value is a sequence of numbers.
123
String
String value is a sequence of letters, symbols, and spaces enclosed in double quotation marks.
"this is a string"
Boolean
Boolean value is either true
or false
.
true
Array
Arrays are composed of expressions.
[1, "string", true]
Identifiers
Strings beginning with the $
symbol are considered as identifiers.
"$x"
Embedding the Identifier in a string is accomplished by using curly brackets.
"{$hello}, world!"
Assignment
To assign a value or function to an identifier, use the set
key.
parent key | children key | explanation |
---|---|---|
set | declaration of assignment | |
var | identifier name | |
val | value to assign |
[
{
"set": {
"var": "$x",
"val": 10
}
},
"$x"
]
Function
Function Definition
Functions can be defined by using set
key and lambda
expression.
parent key | children key | explanation |
---|---|---|
lambda | declaration | |
params | parameters(optional) | |
body | body of function |
{
"set": {
"var": "$add",
"val": {
"lambda": {
"params": ["$x", "$y"],
"body": {
"command": {
"symbol": "+",
"args": ["$x", "$y"]
}
}
}
}
}
}
Function Call
Functions can be called by using command
key.
parent key | children key | explanation |
---|---|---|
command | declaration of function calling | |
symbol | function to call | |
args | arguments(optional) |
{
"command": {
"symbol": "+",
"args": [1, 2]
}
}
Builtin Functions
Builtin functions are as follows,
関数 | explanation |
---|---|
+ | addition |
- | subtraction |
* | multiplication |
/ | division |
% | modulo |
! | negation |
&& | and operation |
\ | \ |
== | equation |
!= | non equation |
> | greater than |
>= | greater than equal |
< | smaller than |
>= | smaller than equal |
print to standard output | |
len | length of array |
at | access to the element of array |
If
Conditional branches can be implemented by using the if
key.
parent key | children key | explanation |
---|---|---|
if | declaratoin of if | |
cond | condition | |
conseq | consequence(the program to execute when cond is true) | |
alt | alternative(the program to execute when cond is false) |
{
"if": {
"cond": {
"command": {
"symbol": "==",
"args": [1, 2]
}
},
"conseq": {
"command": {
"symbol": "+",
"args": [3, 4]
}
},
"alt": {
"command": {
"symbol": "*",
"args": [5, 6]
}
}
}
}
Loop
Iterations are handled by using the loop
key.
parent key | children key | explanation |
---|---|---|
loop | declaration of loop | |
for | the identifier for loop counter | |
from | the initial value of loop counter | |
until | loop termination condition (break when loop counter equals this value) | |
do | Iterative processing body |
{
"loop": {
"for": "$i",
"from": 0,
"until": 10,
"do": {
"command": {
"symbol": "print",
"args": "$i"
}
}
}
}
You can also perform a loop operation on the elements of an Array. Unlike the example above, the in
key specifies an Array.
[
{
"set": {
"var": "$arr",
"val": [10, 20, 30]
}
},
{
"loop": {
"for": "$element",
"in": "$arr",
"do": {
"command": {
"symbol": "print",
"args": "$element"
}
}
}
}
]
Also, insert break
and continue
as keys as follows.
[
{
"set": {
"var": "$sum",
"val": 0
}
},
{
"loop": {
"for": "$i",
"from": 1,
"until": 15,
"do": {
"if": {
"cond": {
"command": {
"symbol": ">",
"args": ["$i", 10]
}
},
"conseq": {
"break": {}
},
"alt": {
"if": {
"cond": {
"command": {
"symbol": "==",
"args": [
{
"command": {
"symbol": "%",
"args": ["$i", 2]
}
},
0
]
}
},
"conseq": {
"set": {
"var": "$sum",
"val": {
"command": {
"symbol": "+",
"args": ["$sum", "$i"]
}
}
}
},
"alt": {
"continue": {}
}
}
}
}
}
}
},
"$sum"
]
Retrun
Use return
key when you exit the program with return.
[
{
"set": {
"var": "$f",
"val": {
"lambda": {
"body": [
{
"set": {
"var": "$sum",
"val": 0
}
},
{
"loop": {
"for": "$i",
"from": 1,
"until": 11,
"do": {
"if": {
"cond": {
"command": {
"symbol": ">",
"args": ["$i", 5]
}
},
"conseq": {
"return": "$sum"
},
"alt": {
"set": {
"var": "$sum",
"val": {
"command": {
"symbol": "+",
"args": ["$sum", "$i"]
}
}
}
}
}
}
}
}
]
}
}
}
},
{
"command": {
"symbol": "$f"
}
}
]
Macro
Macro can be defined by using defmacro
key.
parent key | children key | explanation |
---|---|---|
defmacro | declaration of macro definition | |
name | name of macro | |
keys | keys | |
body | the body of macro |
You can also call the quote
symbol for quoting, and unquote by adding backquotes to the beginning of the string.
{
"defmacro": {
"name": "unless",
"keys": ["cond", "conseq", "alt"],
"body": {
"command": {
"symbol": "quote",
"args": {
"if": {
"cond": {
"command": {
"symbol": "!",
"args": ",cond"
}
},
"conseq": ",conseq",
"alt": ",alt"
}
}
}
}
}
}
Defining function can be much simpler if you use Macro.
[
{
"defmacro": {
"name": "defun",
"keys": ["name", "params", "body"],
"body": {
"command": {
"symbol": "quote",
"args": {
"set": {
"var": ",name",
"val": {
"lambda": {
"params": ",params",
"body": ",body"
}
}
}
}
}
}
}
},
{
"defun": {
"name": "$add",
"params": ["$x", "$y"],
"body": {
"command": {
"symbol": "+",
"args": ["$x", "$y"]
}
}
}
},
{
"command": {
"symbol": "$add",
"args": [1, 2]
}
}
]
Comment
Comments can be inesrted by using //
key.
{
"//": "this is a function to add two values",
"set": {
"var": "$add",
"val": {
"lambda": {
"params": ["$x", "$y"],
"body": {
"command": {
"symbol": "+",
"args": ["$x", "$y"]
}
}
}
}
}
}
🤔 FizzBuzz Problem
Finally, I have included an example of solving a FizzBuzz problem in JSOP.
[
{
"set": {
"var": "$num",
"val": 31
}
},
{
"loop": {
"for": "$i",
"from": 1,
"until": "$num",
"do": {
"if": {
"cond": {
"command": {
"symbol": "&&",
"args": [
{
"command": {
"symbol": "==",
"args": [
{
"command": {
"symbol": "%",
"args": ["$i", 3]
}
},
0
]
}
},
{
"command": {
"symbol": "==",
"args": [
{
"command": {
"symbol": "%",
"args": ["$i", 5]
}
},
0
]
}
}
]
}
},
"conseq": {
"command": {
"symbol": "print",
"args": "{$i}: FizzBuzz"
}
},
"alt": {
"if": {
"cond": {
"command": {
"symbol": "==",
"args": [
{
"command": {
"symbol": "%",
"args": ["$i", 3]
}
},
0
]
}
},
"conseq": {
"command": {
"symbol": "print",
"args": "{$i}: Fizz"
}
},
"alt": {
"if": {
"cond": {
"command": {
"symbol": "==",
"args": [
{
"command": {
"symbol": "%",
"args": ["$i", 5]
}
},
0
]
}
},
"conseq": {
"command": {
"symbol": "print",
"args": "{$i}: Buzz"
}
},
"alt": {
"command": {
"symbol": "print",
"args": "$i"
}
}
}
}
}
}
}
}
}
}
]
🎬 At the end
I started JSOP in imitation of LISP, and I like the fact that it can be easily metaprogrammed by using macros just like LISP. As you can see from the solution to the FizzBuzz problem, I think it is not a practical language.
I would be happy to get your advice/feedback. I am looking forward to your comments!