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!
Top comments (0)