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
.jsopand.jsop.jsonare 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!
Top comments (0)