nova>> print("Hi! Welcome to Nova! :)");
Hi! Welcome to Nova! :)
For a detailed description of the usage of Nova, please visit the Nova Wiki
- Download the latest release from Github releases
- Untar with
tar -xf nova-<latest-version>.tar.gz
- Configure for your system with
./configure
- Install to your system with
make install
(if permission denied, trysudo make install
) - Run nova:
nova <filename>.nov
ornova
to enter the shell
Compiling/Building:
- Option 1: Use GNUWin32 to use make
- Option 2: Install make.exe from the GNU website
- Option 3: Install
chocolatey
and the runchoco install make
- Option 4: Manually build all the source files by installing g++, and compiling all src files.
The steps in the Linux/MacOS section can now be followed
- Clone this repository over https or SSH :
git clone https://github.com/sriharivishnu/Nova.git
orgit clone [email protected]:sriharivishnu/Nova.git
- Navigate to the directory that was created
- run
autoreconf --install
- run
./configure
- run
make
to create a binary in the current directory ormake install
to install it to your system - Run the executable from the command line with
./nova
(ornova
if installed) to start the shell, or type in./nova <filename>.nov
to run a file.
- Please follow the steps above after installing the necessary GNU tools (autoreconf, make etc.)
- For a detailed description of the current syntax, please view the Nova Wiki
Input Text -> Lexer -> Parser -> Interpreter -> Output
A standard lexer to parse the string into a stream of tokens such as plus
, minus
, while
etc. Relevant methods, other than the single character tokens, are makeNumber()
, makeString()
, makeIdentifier()
which makes a number, string, and identifier tokens respectively.
The function of the parser is more interesting than that of the Lexer. The Parser's task is to turn the stream of tokens outputted by the Lexer into a data structure called an AST (Abstract Syntax Tree), a format that allows the interpreter to execute the code.
The two main components of the parser are the parseStatement()
and parseExpression()
methods. The difference between a statement and an expression is that a Statement does not return a value, while an expression does.
Consider the following statement:
while (a < 0) {
b = a * 10 + 1;
a--;
}
Notice that the while statement does not return a value, however, expressions such as a<0
or b = a*10+1
(since assignment returns the assigned value in Nova) return a value.
To parse the above statement, we start with a call to parseStatement(). The parseStatement() recognizes that it is a while statement since it starts with the keyword 'while'. It then looks for an expression for its condition, in this case it is a<0
. If it does not find a condition/expression, then it throws an error. After the call to parseExpression() returns an expression, the original parseStatement() method now makes a recursive call to parseStatement() to parse the next statement. The '{' signifies a block statement, and it then keeps recursively parsing statements and expressions likewise.
The parseExpression()
method is more complicated, since one must consider the order of operations as well. Here is a brief description of the Pratt Parsing algorithm:
- Assign all operators a precedence value.
- Parse a 'prefix expression', which can be (++, a string, number) anything that can occur first in an expression. (if not found, throw error)
- While the precedence of the next 'infix operator' is less than the precedence of the next token, keep parsing the expression.
1 + 2 * (5+3)
shared_ptr<Expression> Parser::parseExpression(int precedence) {
Token token = consume();
shared_ptr<Expression> left = getPrefixExpression(token);
while (precedence < getPrecedence()) {
token = consume();
left = getInfixExpression(left, token);
}
return left;
}
The parseStatement()
method is much simpler, looking for a keyword such as 'while' or 'for', and if none is found, it goes to the default simple statement, which is just a wrapper for an expression.
All expressions are implemented with the visitor pattern, implementing an accept method, which directs the parse to a centralized class called a visitor. This makes it very easy to implement new expressions.
The interpreter goes through all statements, and executes them. Each statement implements an execute() method, which invokes its function.
- User-defined operations