Aims
The aims of this project are as follows:

To encourage you to use regular expressions to implement a trivial scanner.

To make you implement a recursive-descent parser for a small language.

To use JSON to represent the results of parsing.

Requirements
Use the implementation of either java, node or python3 available on remote.cs to implement a parser for the following language of which is a subset of the syntax used for elixir data literals. (You may also use a language like TypeScript which compiles to one of these languages as long as you provide all the steps needed to compile that language to one of the above languages).

A sentence in the language consists of a sequence of zero-or-more data-literal's.

A data-literal is either a list literal, a tuple literal, a map literal, or a primitive literal.

A primitive literal is either an integer, atom or boolean.

A list literal is a sequence of 0-or-more comma-separated data-literal's within square brackets [ and ].

A tuple literal is a sequence of 0-or-more comma-separated data-literal's within braces { and }.

A map literal is a sequence of 0-or-more comma-separated key-pair's within a decorated left brace %{ and a regular right brace }.

A key-pair is either a sequence consisting of a data-literal, a right-arrow => followed by a data-literal, or a sequence consisting of a key followed by a data-literal.

An integer consists of a sequence of one-or-more digits, possibly containing internal underscores _.

An atom consists of a colon :, followed by an alphabetic character or underscore _ followed by a sequence of zero-or-more alphanumeric characters or underscores _.

A key is just like an atom but the : must be at the end instead of the start.

A boolean is one of the words true or false.

Whitespace and #-to-end-of-line comments should be ignored.

Note that a map literal of the form %{ key: 22 } is syntactic sugar for %{ :key => 22 }.

You will specifically need to submit a zip-archive which unpacks into a prj1-sol directory minimally containing two shell scripts make.sh and run.sh:

Running make.sh from any directory should build any artifacts needed to run your program within the prj1-sol directory.

Running run.sh from any directory should read and parse a sentence of the above language from standard input and output on standard output a single line containing the JSON representation of the parse.

Different kinds of data literals should be output as a JSON object having two properties:

%k
The kind of the literal, as defined below.

%v
The value of the literal, as defined below.

The top-level JSON should consist of a JSON list containing the JSON representations of the top-level data-literal's read from standard input.

The JSON representation of the different kind of literals should be as follows:

An integer has the JSON representation { "%k": "int", "%v": value } where value is a JSON integer respresenting the value of the integer. For example, the integer 123 should have the JSON representation { "%k": "int", "%v": 123 }.

An atom has the JSON representation { "%k": "atom", "%v": value } where value is a JSON string spelling out the atom. For example, the atom :_a32 should have the JSON representation { "%k": "atom", "%v": ":_a32" }.

A boolean has the JSON representation { "%k": "bool", "%v": value } where value is a JSON boolean representing the value of the boolean. For example, the boolean true should have the JSON representation { "%k": "bool", "%v": true }.

A key has the JSON representation { "%k": "atom", "%v": value } where value is a JSON string spelling out the key lexeme, but with the : moved to the front. For example, the key abc: should have the JSON representation { "%k": "atom", "%v": ":abc" }.

A list has the JSON representation { "%k": "list", "%v": value } where value is a JSON list containing the JSON representations of the individual items in the list. For example, the list [ 1, 2 ] should have the JSON representation:

	{ "%k": "list",
	  "%v": [
	     { "%k": "int", "%v": 1 },
	     { "%k": "int", "%v": 2 }
	  ]
	}
A tuple has the JSON representation { "%k": "tuple", "%v": value } where value is a JSON list containing the JSON representations of the individual items in the tuple. For example, the tuple { 1, :k } should have the JSON representation:

	{ "%k": "tuple",
	  "%v": [
	     { "%k": "int", "%v": 1 },
	     { "%k": "atom", "%v": ":k" }
	  ]
	}
A map has the JSON representation { "%k": "map", "%v": value } where value is a JSON list containing the 2-element JSON lists representing the individual elements in the map. For example, the map %{ :a => 22, b: 33 } should have the JSON representation:

	{ "%k": "map",
	  "%v": [
	     [ { "%k": "atom", "%v": ":a" },
	       { "%k": "int", "%v": 22 }
	     ],
	     [ { "%k": "atom", "%v": ":b" },
	       { "%k": "int", "%v": 33 }
	     ]
	  ]
	}
The JSON output should consist of a single line without any whitespace other than the newline terminator. The members of a JSON object may be output in any order.

If there are errors in the content, the program should exit with a non-zero status after detecting the first syntax error. It should output a suitable error message on standard error.

An annotated log of the running project and the provided tests should help clarify the above requirements.

Rationale for the Requirements
The requirements are based on the following rationale:

The specified language is a simple language containing a subset of Elixir data literals. Implementing a parser for this language allows you to understand the basic principles of scanning and recursive-descent parsing.

The make.sh and run.sh scripts allow automatic testing of your project without needing to know the details of your implementation language. The former allows the testing program to run any compilation step required by your implementation language and the latter allows the testing program to run the project.


Standard Input, Standard Output, Standard Error
This project requires your program to read from standard input and write its output to standard output and write error messages to standard error. These are the three I/O streams which are initially available when a program starts up under any current OS:

Standard Input
An input stream, initially set up to read from the console. This often corresponds to file descriptor 0.

Standard Output
An output stream, initially set up to output to the console. This often corresponds to file descriptor 1.

Standard Error
Another output stream, initially set up to output to the console. This often corresponds to file descriptor 2.

So you can use these streams without needing to open any file, as they are already open.

All popular languages provide access to these streams.

Python
sys.stdin, sys.stdout and sys.stderr refer to the three streams.

sys.stdin.read() will read from standard input until EOF.

print(...) or sys.stdout.write(...) will print ... to standard output (the former adds a newline).

sys.stderr.write(...) or print(..., file=sys.stderr) will write ... to standard error.

JavaScript nodejs
0, 1 and 2 refer to the three streams and can be used wherever a file path is expected.

fs.readFileSync(0, 'utf8') will read from standard input until EOF.

console.log(...) or fs.writeFileSync(1, ...) will write ... to standard output (the former adds a newline and has additional functionality).

console.error(...) or fs.writeFileSync(2, ...) will write ... to standard error (the former adds a newline and has additional functionality).

Java
Java defines System.in, System.out and System.err for the three streams; you can then use the smorgasbord of java.io.* classes to read/write the streams. The newer java.nio.file package provides more convenient APIs.

Using stdin within the Unix Shell
If a program is reading interactively from standard input, then it will freeze and wait for input to be provided on the terminal:

$ ./run.sh
%{a: 22 }
^D   #indicate EOF
[{"%k":"map","%v":[[{"%k":"atom","%v":":a"},{"%k":"int","%v":22}]]}]
$ 
The control-D is used to indicate EOF to the terminal controller.

It is much more convenient to use I/O redirection in the shell:

$ ./run.sh \
     < ~/cs571/projects/prj1/extras/tests/50-compound-data.test \
   | jq - S . > compound-data.json
The \ escapes newlines to combine multiple physical lines into a single logical line; the < .../tests/50-compound-data.test redirects the contents of 50-compound-data.test to the standard input of run.sh; the | jq -S . pipes the single line output of the program to jq -S . which pretty-prints the json on its standard output (-S sorts all object keys); finally, the > compound-data.json redirects the standard output of jq to compound-data.json.

Note that run.sh is totally unaware of the redirection; the shell takes care of setting up the standard input and output streams so that they are redirected to the files. For example, if run.sh is calling a python parser, then the python parser can continue using sys.stdin and sys.stdout.

Before Starting Your Project
Before starting this project, set up a symlink to the course repository under your home directory on remote.cs:

$ cd ~                    #ensure you are in your home directory
$ ln -s ~umrigar/cs571 .  #set up symlink
$ ls cs571                #list files
You should see the top-level files and directories in the course repository.

Note that the above symlink will always reflect the current contents of the course repository.

It is also probably a good idea to set up a work directory on remote.cs.

$ mkdir -p ~/i571/submit
Then use the ~/i571 directory for any personal work for the course and the ~/i571/submit directory for work to be submitted. For example, you might do the work for this project in ~/i571/submit/prj1-sol.

If you are familiar with git, it is probably a good idea to create a git respository for your ~/i571 directory.


Submission
You are required to submit a zip-archive such that unpacking that archive will result in at least the two following files

prj1-sol/make.sh
prj1-sol/run.sh
The unpacked prj1-sol directory should contain all other source files needed to build and run your project. Note that it must not contain files like *.class files which are built by make.sh.

Important Note: Unfortunately, gradescope removes execute permissions when unpacking the zip archive. So you cannot set up your run.sh script to run interpeter files directly. So for example, a run.sh which contains the line $dir/elixir-literal.mjs will not work, you will need to use node $dir/elixir-literal.mjs. Similarly, for Python use python3 $dir/elixir-literal.py instead of $dir/elixir-literal.py.