added table of contents to docs
This commit is contained in:
+21
-14
@@ -2,6 +2,13 @@
|
|||||||
# The Noja language
|
# The Noja language
|
||||||
|
|
||||||
## Table of contents
|
## Table of contents
|
||||||
|
1. [Introduction](#introduction)
|
||||||
|
2. [Implementation overview](#implementation-overview)
|
||||||
|
3. [The first program](#the-first-program)
|
||||||
|
4. [Expressions](#expressions)
|
||||||
|
5. [Branches](#branches)
|
||||||
|
6. [Loops](#loops)
|
||||||
|
7. [Functions](#functions)
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
|
|
||||||
@@ -31,6 +38,7 @@ does things like:
|
|||||||
- referring to instructions by their index.
|
- referring to instructions by their index.
|
||||||
|
|
||||||
For example, by compiling the following snippet
|
For example, by compiling the following snippet
|
||||||
|
|
||||||
```py
|
```py
|
||||||
define = true;
|
define = true;
|
||||||
|
|
||||||
@@ -39,7 +47,9 @@ if define:
|
|||||||
|
|
||||||
print(a, '\n');
|
print(a, '\n');
|
||||||
```
|
```
|
||||||
|
|
||||||
one would obtain the following bytecode:
|
one would obtain the following bytecode:
|
||||||
|
|
||||||
```
|
```
|
||||||
0: PUSHTRU
|
0: PUSHTRU
|
||||||
1: ASS "define"
|
1: ASS "define"
|
||||||
@@ -57,10 +67,11 @@ one would obtain the following bytecode:
|
|||||||
13: RETURN
|
13: RETURN
|
||||||
|
|
||||||
```
|
```
|
||||||
as you can see, there are instructions like ASS and PUSHVAR that
|
|
||||||
|
as you can see, there are instructions like `ASS` and `PUSHVAR` that
|
||||||
assign to and read from variables by specifying names, and jumps
|
assign to and read from variables by specifying names, and jumps
|
||||||
that refer to other points of the "executable" by specifying indices
|
that refer to other points of the "executable" by specifying indices
|
||||||
(like JUMPIFNOTANDPOP) instead of raw addresses.
|
(like `JUMPIFNOTANDPOP`) instead of raw addresses.
|
||||||
|
|
||||||
All values (objects) are allocated on a garbage-collected heap.
|
All values (objects) are allocated on a garbage-collected heap.
|
||||||
For this reason all variables are simply references to these objects.
|
For this reason all variables are simply references to these objects.
|
||||||
@@ -84,19 +95,22 @@ is a list of statements that can be of multiple kinds:
|
|||||||
- composit statements
|
- composit statements
|
||||||
|
|
||||||
In general, unless it's inside strings, whitespace is ignored and
|
In general, unless it's inside strings, whitespace is ignored and
|
||||||
comments start with the # character.
|
comments start with the `#` character.
|
||||||
|
|
||||||
The most basic yet interesting program is:
|
The most basic yet interesting program is:
|
||||||
|
|
||||||
```py
|
```py
|
||||||
print('Hello, world!\n');
|
print('Hello, world!\n');
|
||||||
```
|
```
|
||||||
|
|
||||||
as in other languages, this kind of statement is an expression.
|
as in other languages, this kind of statement is an expression.
|
||||||
Expression statements require a ';' to determine their end.
|
Expression statements require a ';' to determine their end.
|
||||||
|
|
||||||
The print function can take any number of arguments of any type
|
The print function can take any number of arguments of any type
|
||||||
and doesn't add any spaces or newlines to the output.
|
and doesn't add any spaces or newlines to the output.
|
||||||
|
|
||||||
```py
|
```py
|
||||||
print(1, 2, 3, '\n');
|
print(1, 2, 3, true, '\n');
|
||||||
```
|
```
|
||||||
|
|
||||||
## Expressions
|
## Expressions
|
||||||
@@ -160,12 +174,6 @@ print(6 != 6, '\n'); # false
|
|||||||
The equal and not equal operators are available on every type of object,
|
The equal and not equal operators are available on every type of object,
|
||||||
while the others are only available for numeric types.
|
while the others are only available for numeric types.
|
||||||
|
|
||||||
### Booleans
|
|
||||||
TODO
|
|
||||||
|
|
||||||
### None
|
|
||||||
TODO
|
|
||||||
|
|
||||||
## Branches
|
## Branches
|
||||||
|
|
||||||
It's possible to make the execution of a statement optional, based on the
|
It's possible to make the execution of a statement optional, based on the
|
||||||
@@ -265,12 +273,11 @@ body are shared with the parent's context.
|
|||||||
Functions can be defined using the following syntax:
|
Functions can be defined using the following syntax:
|
||||||
|
|
||||||
```py
|
```py
|
||||||
# Define it ..
|
# Define it
|
||||||
fun say_hello_to(name)
|
fun say_hello_to(name)
|
||||||
print('Hello, ', name, '!\n\n');
|
print('Hello, ', name, '!\n\n');
|
||||||
|
|
||||||
# .. call it.
|
# .. and then call it.
|
||||||
|
|
||||||
say_hello_to('Francesco');
|
say_hello_to('Francesco');
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -318,7 +325,7 @@ test_func = 5;
|
|||||||
# The following line, if executed, returns an error because the test_func
|
# The following line, if executed, returns an error because the test_func
|
||||||
# identifier is now associated to 5, which is not a function.
|
# identifier is now associated to 5, which is not a function.
|
||||||
|
|
||||||
# test_func();
|
test_func(); # Error!!
|
||||||
```
|
```
|
||||||
|
|
||||||
Functions can return values exactly like in other languages:
|
Functions can return values exactly like in other languages:
|
||||||
|
|||||||
@@ -1,71 +0,0 @@
|
|||||||
|
|
||||||
# ------------------------------------------------------------------------- #
|
|
||||||
# --- Introduction -------------------------------------------------------- #
|
|
||||||
#
|
|
||||||
# This language was written as a personal study of how interpreters
|
|
||||||
# and compilers work. For this reason, the language is very basic.
|
|
||||||
# One of the main inspirations was the CPython's source code since
|
|
||||||
# it's extremely readable and has a very simple and clean architecture.
|
|
||||||
#
|
|
||||||
# This file was intended for people who already program in other
|
|
||||||
# high level languages (such as Python, Javascript, Ruby) and don't
|
|
||||||
# need to be introduced to basic programming concepts (variables,
|
|
||||||
# expressions and branches). This way, there is more space for the
|
|
||||||
# comparison of the language's features with the mainstream languages.
|
|
||||||
#
|
|
||||||
# ------------------------------------------------------------------------- #
|
|
||||||
# --- Implementation ------------------------------------------------------ #
|
|
||||||
#
|
|
||||||
# The interpreter works by compiling the provided source to a bytecode
|
|
||||||
# format and executing it. The bytecode is very high level since it
|
|
||||||
# does things like:
|
|
||||||
#
|
|
||||||
# - explicitly referring to variables by name.
|
|
||||||
#
|
|
||||||
# - treating values as atomic things: from the perspective of the
|
|
||||||
# bytecode, a list and an integer occupy the same space on the
|
|
||||||
# stack, which is 1.
|
|
||||||
#
|
|
||||||
# - referring to instructions by their index.
|
|
||||||
#
|
|
||||||
# For example, by compiling the following snippet
|
|
||||||
|
|
||||||
define = true;
|
|
||||||
|
|
||||||
if define:
|
|
||||||
a = 33;
|
|
||||||
|
|
||||||
print(a, '\n');
|
|
||||||
|
|
||||||
# one would obtain the following bytecode:
|
|
||||||
#
|
|
||||||
# 0: PUSHTRU
|
|
||||||
# 1: ASS "define"
|
|
||||||
# 2: POP 1
|
|
||||||
# 3: PUSHVAR "define"
|
|
||||||
# 4: JUMPIFNOTANDPOP 8
|
|
||||||
# 5: PUSHINT 33
|
|
||||||
# 6: ASS "a"
|
|
||||||
# 7: POP 1
|
|
||||||
# 8: PUSHSTR "\n"
|
|
||||||
# 9: PUSHVAR "a"
|
|
||||||
# 10: PUSHVAR "print"
|
|
||||||
# 11: CALL 2
|
|
||||||
# 12: POP 1
|
|
||||||
# 13: RETURN
|
|
||||||
#
|
|
||||||
# as you can see, there are instructions like ASS and PUSHVAR that
|
|
||||||
# assign to and read from variables by specifying names, and jumps
|
|
||||||
# that refer to other points of the "executable" by specifying indices
|
|
||||||
# (like JUMPIFNOTANDPOP) instead of raw addresses.
|
|
||||||
#
|
|
||||||
# All values (objects) are allocated on a garbage-collected heap.
|
|
||||||
# For this reason all variables are simply references to these objects.
|
|
||||||
# The garbage collection algorithm is a copy-and-compact one. It
|
|
||||||
# behaves as a bump-pointer allocator until there is space left,
|
|
||||||
# and when space runs out, it creates a new heap, copies all of the
|
|
||||||
# alive object into it, calls the destructors of the dead objects
|
|
||||||
# and frees the old one.
|
|
||||||
#
|
|
||||||
# ------------------------------------------------------------------------- #
|
|
||||||
# ------------------------------------------------------------------------- #
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
|
|
||||||
# ------------------------------------------------------------------------- #
|
|
||||||
# --- The first program --------------------------------------------------- #
|
|
||||||
#
|
|
||||||
# The sintax is similar to Python's but is more C-like. A Noja script
|
|
||||||
# is a list of statements that can be of multiple kinds:
|
|
||||||
#
|
|
||||||
# - function declaractions
|
|
||||||
# - expressions
|
|
||||||
# - if-else branches
|
|
||||||
# - while loops
|
|
||||||
# - do-while loops
|
|
||||||
# - return statements
|
|
||||||
# - composit statements
|
|
||||||
#
|
|
||||||
# In general, unless it's inside strings, whitespace is ignored and
|
|
||||||
# comments start with the # character.
|
|
||||||
#
|
|
||||||
# The most basic yet interesting program is:
|
|
||||||
|
|
||||||
print('Hello, world!\n');
|
|
||||||
|
|
||||||
# as in other languages, this kind of statement is an expression.
|
|
||||||
# Expression statements require a ';' to determine their end.
|
|
||||||
#
|
|
||||||
# The print function can take any number of arguments of any type
|
|
||||||
# and doesn't add any spaces or newlines to the output.
|
|
||||||
|
|
||||||
print(1, 2, 3, '\n');
|
|
||||||
|
|
||||||
#
|
|
||||||
# ------------------------------------------------------------------------- #
|
|
||||||
# --- Variables and expressions ------------------------------------------- #
|
|
||||||
#
|
|
||||||
# You can set variables without declaring them first by using the
|
|
||||||
# assignment operator:
|
|
||||||
|
|
||||||
a = 5;
|
|
||||||
|
|
||||||
# which is similar to Python's assignment, but is a little different.
|
|
||||||
# In this language, assignments are considered as expressions, in fact
|
|
||||||
# you can do things like
|
|
||||||
|
|
||||||
a = (b = 1) + 1;
|
|
||||||
|
|
||||||
# The value resulting from an assignment is the assigned value.
|
|
||||||
# After this expression, b's value is 1 and a's value is 2.
|
|
||||||
|
|
||||||
print('b = ', b, '\n'); # b = 1
|
|
||||||
print('a = ', a, '\n'); # a = 2
|
|
||||||
|
|
||||||
# all of the basic arithmetic operators are available:
|
|
||||||
|
|
||||||
x = 1 + 1;
|
|
||||||
y = 1 - 2;
|
|
||||||
z = 3 * 2;
|
|
||||||
w = 10 / 3;
|
|
||||||
|
|
||||||
print('x = ', x, '\n'); # x = 2
|
|
||||||
print('y = ', y, '\n'); # y = -1
|
|
||||||
print('z = ', z, '\n'); # z = 6
|
|
||||||
print('w = ', w, '\n'); # w = 3
|
|
||||||
|
|
||||||
# Note how the division returns the rounded down version of the result.
|
|
||||||
# This is because the division was performed on integers. By making one
|
|
||||||
# of the operands a floating point value, also a floating point result
|
|
||||||
# is returned:
|
|
||||||
|
|
||||||
w = 10 / 3.0;
|
|
||||||
|
|
||||||
print('w = ', w, '\n');
|
|
||||||
|
|
||||||
# Arithmetic operators are only available for numeric types of objects.
|
|
||||||
# If you try to apply them on other kinds of types, you get a runtime
|
|
||||||
# error:
|
|
||||||
|
|
||||||
# (Uncomment the following line and run this file to get the error)
|
|
||||||
# p = 5 + 'hello';
|
|
||||||
|
|
||||||
# And relational operators are also available:
|
|
||||||
|
|
||||||
print(1 < 2, '\n'); # true
|
|
||||||
print(1 > 2, '\n'); # false
|
|
||||||
|
|
||||||
print(1 >= 0, '\n'); # true
|
|
||||||
print(1 <= 0, '\n'); # false
|
|
||||||
|
|
||||||
print(1 == 5, '\n'); # false
|
|
||||||
print(6 == 6, '\n'); # true
|
|
||||||
|
|
||||||
print(1 != 5, '\n'); # true
|
|
||||||
print(6 != 6, '\n'); # false
|
|
||||||
|
|
||||||
# The equal and not equal operators are available on every type of object,
|
|
||||||
# while the others are only available for numeric types.
|
|
||||||
#
|
|
||||||
# ------------------------------------------------------------------------- #
|
|
||||||
# --- The boolean type ---------------------------------------------------- #
|
|
||||||
#
|
|
||||||
|
|
||||||
# TODO
|
|
||||||
|
|
||||||
#
|
|
||||||
# ------------------------------------------------------------------------- #
|
|
||||||
# --- The none value ------------------------------------------------------ #
|
|
||||||
#
|
|
||||||
|
|
||||||
# TODO
|
|
||||||
|
|
||||||
#
|
|
||||||
# ------------------------------------------------------------------------- #
|
|
||||||
# ------------------------------------------------------------------------- #
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
|
|
||||||
# ------------------------------------------------------------------------- #
|
|
||||||
# --- Branches ------------------------------------------------------------ #
|
|
||||||
#
|
|
||||||
# It's possible to make the execution of a statement optional, based on the
|
|
||||||
# result of an expression. Like in other languages, you do this using if-else
|
|
||||||
# statements:
|
|
||||||
|
|
||||||
if 1 < 2:
|
|
||||||
print('Took the branch!\n'); # This is executed!
|
|
||||||
|
|
||||||
if 1 > 2:
|
|
||||||
print('Didn\'t take the branch\n'); # This isn't!
|
|
||||||
|
|
||||||
# or you can specify an alternative branch, which is executed when the
|
|
||||||
# condition isn't true:
|
|
||||||
|
|
||||||
if 1 > 2:
|
|
||||||
print('Not executed..\n');
|
|
||||||
else
|
|
||||||
print('Executed!\n');
|
|
||||||
|
|
||||||
# You can have multiple statements inside a branch by having them inside a
|
|
||||||
# compound statement. Compound statements are statement lists wrapped inside
|
|
||||||
# curly brackets, like this:
|
|
||||||
|
|
||||||
{ print('Hello from a '); print('compound statement!\n'); }
|
|
||||||
|
|
||||||
# This way they count as one statement.
|
|
||||||
|
|
||||||
if 1 == 1:
|
|
||||||
{
|
|
||||||
print('Executed\n');
|
|
||||||
print('Also executed\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
# Variables defined inside an if-else statement's branch are defined
|
|
||||||
# in the parent's context. This implies that variables may or may not
|
|
||||||
# be defined when you access them, based on which branch is taken.
|
|
||||||
|
|
||||||
a = 1;
|
|
||||||
|
|
||||||
if a < 2:
|
|
||||||
x = 100;
|
|
||||||
|
|
||||||
# Now x is defined, but if "a" were to be higher or equal to 2, it
|
|
||||||
# wouldn't be defined and the runtime would return an error.
|
|
||||||
#
|
|
||||||
# ------------------------------------------------------------------------- #
|
|
||||||
# ------------------------------------------------------------------------- #
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
|
|
||||||
# ------------------------------------------------------------------------- #
|
|
||||||
# --- Loops --------------------------------------------------------------- #
|
|
||||||
#
|
|
||||||
# Looping constructs are available in the form of while and do-while
|
|
||||||
# statements. The while statement checks the condition before each
|
|
||||||
# iteration:
|
|
||||||
|
|
||||||
i = 0;
|
|
||||||
while i < 10:
|
|
||||||
i = i + 1;
|
|
||||||
|
|
||||||
# This loop runs for 10 times. As for the if-else statement, a single
|
|
||||||
# statement is expected as the body of the while statement. You can
|
|
||||||
# provide it a compound statement tho.
|
|
||||||
|
|
||||||
i = 0;
|
|
||||||
while i < 10:
|
|
||||||
{
|
|
||||||
print('While iteration no. ', i, '\n');
|
|
||||||
i = i + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
# The do-while statement checks the condition at the end of each
|
|
||||||
# iteration. This means that at least one iteration is performed!
|
|
||||||
|
|
||||||
i = 0;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
print('Do-while iteration no. ', i, '\n');
|
|
||||||
i = i + 1;
|
|
||||||
}
|
|
||||||
while i < 10;
|
|
||||||
|
|
||||||
# Like for if-else statements, variables defined inside the loop
|
|
||||||
# body are shared with the parent's context.
|
|
||||||
#
|
|
||||||
# ------------------------------------------------------------------------- #
|
|
||||||
# ------------------------------------------------------------------------- #
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
|
|
||||||
# ------------------------------------------------------------------------- #
|
|
||||||
# --- Functions ----------------------------------------------------------- #
|
|
||||||
|
|
||||||
# Functions can be defined using the following syntax:
|
|
||||||
|
|
||||||
fun say_hello_to(name)
|
|
||||||
print('Hello, ', name, '!\n\n');
|
|
||||||
|
|
||||||
# and now we can call it by doing
|
|
||||||
|
|
||||||
say_hello_to('Francesco');
|
|
||||||
|
|
||||||
# Functions can have an arbitrary amount of arguments. If the function is
|
|
||||||
# called with more arguments than it expected, the extra values are thrown
|
|
||||||
# away. If the function is called with less arguments than it expected,
|
|
||||||
# the argument set if filled up with none values.
|
|
||||||
|
|
||||||
fun test_func(a, b, c)
|
|
||||||
{
|
|
||||||
print('a = ', a, '\n');
|
|
||||||
print('b = ', b, '\n');
|
|
||||||
print('c = ', c, '\n\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
test_func();
|
|
||||||
# a = none
|
|
||||||
# b = none
|
|
||||||
# c = none
|
|
||||||
|
|
||||||
test_func(1, 2);
|
|
||||||
# a = 1
|
|
||||||
# b = 2
|
|
||||||
# c = none
|
|
||||||
|
|
||||||
test_func(1, 2, 3);
|
|
||||||
# a = 1
|
|
||||||
# b = 2
|
|
||||||
# c = 3
|
|
||||||
|
|
||||||
test_func(1, 2, 3, 4);
|
|
||||||
# a = 1
|
|
||||||
# b = 2
|
|
||||||
# c = 3
|
|
||||||
|
|
||||||
# Functions are actually variables like the ones that are be defined using
|
|
||||||
# the assignment operator. In fact, you can reassign them new values if you
|
|
||||||
# want.
|
|
||||||
|
|
||||||
test_func = 5;
|
|
||||||
|
|
||||||
# The following line, if executed, returns an error because the test_func
|
|
||||||
# identifier is now associated to 5, which is not a function.
|
|
||||||
|
|
||||||
# test_func();
|
|
||||||
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------- #
|
|
||||||
# --- Returns ------------------------------------------------------------- #
|
|
||||||
|
|
||||||
# Functions can return values exactly like in other languages:
|
|
||||||
|
|
||||||
fun multiply(x, y)
|
|
||||||
return x * y;
|
|
||||||
|
|
||||||
p = 4;
|
|
||||||
q = 7;
|
|
||||||
r = multiply(p, q);
|
|
||||||
|
|
||||||
print(p, ' * ', q, ' = ', r, '\n');
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------- #
|
|
||||||
# --- Scopes -------------------------------------------------------------- #
|
|
||||||
#
|
|
||||||
# Functions are always "pure", in the sense that the only values that the
|
|
||||||
# function body can access are the ones provided as arguments. Usually in
|
|
||||||
# other languages, functions can access the global scope and the parent
|
|
||||||
# scope (closures). There's no such mechanism in this language (at the
|
|
||||||
# moment).
|
|
||||||
#
|
|
||||||
# The only exception is made for the "built in" variables, which are
|
|
||||||
# provided by the runtime of the language and can't be modified by the
|
|
||||||
# user. The print function is one of these variables. One may override
|
|
||||||
# these variables but the effect only lasts for the lifetame of the
|
|
||||||
# context local to the assignment.
|
|
||||||
|
|
||||||
# Overwrite the print variable inside the global scope..
|
|
||||||
print = 5;
|
|
||||||
|
|
||||||
fun test()
|
|
||||||
{
|
|
||||||
# Now call print from inside the function.
|
|
||||||
print('Not overwritten here!\n');
|
|
||||||
|
|
||||||
# If the previous assignment were to overwrite the print function
|
|
||||||
# globally, the previous statement would fail because the value 5
|
|
||||||
# isn't a function.
|
|
||||||
}
|
|
||||||
|
|
||||||
test();
|
|
||||||
|
|
||||||
# Now that i think about it, we lost the reference to the print function
|
|
||||||
# inside this scope. But we can take it back by returning it from a
|
|
||||||
# function!
|
|
||||||
|
|
||||||
fun get_print_back()
|
|
||||||
return print;
|
|
||||||
|
|
||||||
print = get_print_back();
|
|
||||||
|
|
||||||
print('Hei! Print is back!\n');
|
|
||||||
|
|
||||||
# ------------------------------------------------------------------------- #
|
|
||||||
# ------------------------------------------------------------------------- #
|
|
||||||
Reference in New Issue
Block a user