diff --git a/docs/language.md b/docs/language.md index 6136870..acad82a 100644 --- a/docs/language.md +++ b/docs/language.md @@ -2,6 +2,13 @@ # The Noja language ## 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 @@ -31,6 +38,7 @@ does things like: - referring to instructions by their index. For example, by compiling the following snippet + ```py define = true; @@ -39,7 +47,9 @@ if define: print(a, '\n'); ``` + one would obtain the following bytecode: + ``` 0: PUSHTRU 1: ASS "define" @@ -57,10 +67,11 @@ one would obtain the following bytecode: 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 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. 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 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: + ```py 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. + ```py -print(1, 2, 3, '\n'); +print(1, 2, 3, true, '\n'); ``` ## Expressions @@ -160,12 +174,6 @@ 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. -### Booleans -TODO - -### None -TODO - ## Branches 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: ```py -# Define it .. +# Define it fun say_hello_to(name) print('Hello, ', name, '!\n\n'); -# .. call it. - +# .. and then call it. say_hello_to('Francesco'); ``` @@ -318,7 +325,7 @@ 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(); +test_func(); # Error!! ``` Functions can return values exactly like in other languages: diff --git a/samples/000_Overview.noja b/samples/000_Overview.noja deleted file mode 100644 index 27d6473..0000000 --- a/samples/000_Overview.noja +++ /dev/null @@ -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. -# -# ------------------------------------------------------------------------- # -# ------------------------------------------------------------------------- # diff --git a/samples/100_Expressions.noja b/samples/100_Expressions.noja deleted file mode 100644 index 139cb7f..0000000 --- a/samples/100_Expressions.noja +++ /dev/null @@ -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 - -# -# ------------------------------------------------------------------------- # -# ------------------------------------------------------------------------- # diff --git a/samples/150_Branches.noja b/samples/150_Branches.noja deleted file mode 100644 index d6c1935..0000000 --- a/samples/150_Branches.noja +++ /dev/null @@ -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. -# -# ------------------------------------------------------------------------- # -# ------------------------------------------------------------------------- # diff --git a/samples/200_Loops.noja b/samples/200_Loops.noja deleted file mode 100644 index b5f1e6a..0000000 --- a/samples/200_Loops.noja +++ /dev/null @@ -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. -# -# ------------------------------------------------------------------------- # -# ------------------------------------------------------------------------- # diff --git a/samples/250_Functions.noja b/samples/250_Functions.noja deleted file mode 100644 index 8323db4..0000000 --- a/samples/250_Functions.noja +++ /dev/null @@ -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'); - -# ------------------------------------------------------------------------- # -# ------------------------------------------------------------------------- #