From b2dca9c40be84d91dcac9a6d583e537cd4cac65e Mon Sep 17 00:00:00 2001 From: cozis Date: Fri, 5 Nov 2021 15:27:34 +0100 Subject: [PATCH 01/11] working on docs --- docs/language.md | 217 +++++++++++++++++++++++++++++++++++++ samples/250_Functions.noja | 8 +- 2 files changed, 220 insertions(+), 5 deletions(-) create mode 100644 docs/language.md diff --git a/docs/language.md b/docs/language.md new file mode 100644 index 0000000..11da1f6 --- /dev/null +++ b/docs/language.md @@ -0,0 +1,217 @@ + +# The Noja language + +## Table of contents + +## 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 overview + + 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 +```py +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. + +## 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: +```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'); +``` + +## Expressions + +You can set variables without declaring them first by using the +assignment operator: +```py +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 +```py +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. +```py +print('b = ', b, '\n'); # b = 1 +print('a = ', a, '\n'); # a = 2 +``` +all of the basic arithmetic operators are available: +```py +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: +```py +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: +```py +(Uncomment the following line and run this file to get the error) +# p = 5 + 'hello'; +``` +And relational operators are also available: +```py +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. + +### Booleans +TODO + +### None +TODO + +## 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: + +```py +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: +```py +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: +```py +{ print('Hello from a '); print('compound statement!\n'); } +``` +This way they count as one statement. +```py +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. +```py +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/250_Functions.noja b/samples/250_Functions.noja index b1eb051..8323db4 100644 --- a/samples/250_Functions.noja +++ b/samples/250_Functions.noja @@ -1,7 +1,7 @@ # ------------------------------------------------------------------------- # # --- Functions ----------------------------------------------------------- # -# + # Functions can be defined using the following syntax: fun say_hello_to(name) @@ -54,10 +54,10 @@ test_func = 5; # test_func(); -# + # ------------------------------------------------------------------------- # # --- Returns ------------------------------------------------------------- # -# + # Functions can return values exactly like in other languages: fun multiply(x, y) @@ -69,7 +69,6 @@ r = multiply(p, q); print(p, ' * ', q, ' = ', r, '\n'); -# # ------------------------------------------------------------------------- # # --- Scopes -------------------------------------------------------------- # # @@ -111,6 +110,5 @@ print = get_print_back(); print('Hei! Print is back!\n'); -# # ------------------------------------------------------------------------- # # ------------------------------------------------------------------------- # From 3fc918402ec884701061000a7d84f72780a674e9 Mon Sep 17 00:00:00 2001 From: cozis Date: Fri, 5 Nov 2021 15:36:21 +0100 Subject: [PATCH 02/11] working on docs --- docs/language.md | 184 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 171 insertions(+), 13 deletions(-) diff --git a/docs/language.md b/docs/language.md index 11da1f6..6136870 100644 --- a/docs/language.md +++ b/docs/language.md @@ -111,10 +111,10 @@ In this language, assignments are considered as expressions, in fact you can do things like ```py 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. -```py + +# 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 ``` @@ -141,12 +141,9 @@ 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: -```py -(Uncomment the following line and run this file to get the error) -# p = 5 + 'hello'; -``` -And relational operators are also available: +error. + +Relational operators are also available: ```py print(1 < 2, '\n'); # true print(1 > 2, '\n'); # false @@ -182,8 +179,9 @@ if 1 < 2: 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 +..or you can specify an alternative branch, which is executed when the condition isn't true: + ```py if 1 > 2: print('Not executed..\n'); @@ -193,10 +191,12 @@ else 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: + ```py { print('Hello from a '); print('compound statement!\n'); } ``` This way they count as one statement. + ```py if 1 == 1: { @@ -204,14 +204,172 @@ if 1 == 1: 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. + ```py 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. ``` -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. + +## Loops + +Looping constructs are available in the form of while and do-while +statements. The while statement checks the condition before each +iteration: + +```py +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. + +```py +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! + +```py +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. + +## Functions + +Functions can be defined using the following syntax: + +```py +# Define it .. +fun say_hello_to(name) + print('Hello, ', name, '!\n\n'); + +# .. call it. + +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. + +```py +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. + +```py +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(); +``` + +Functions can return values exactly like in other languages: + +```py +fun multiply(x, y) + return x * y; + +p = 4; +q = 7; +r = multiply(p, q); + +print(p, ' * ', q, ' = ', r, '\n'); +``` + +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. + +```py +# 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'); +``` \ No newline at end of file From 795dfbcb54a46ac373951bbeb1732eaa878c25c3 Mon Sep 17 00:00:00 2001 From: cozis Date: Fri, 5 Nov 2021 15:48:28 +0100 Subject: [PATCH 03/11] added table of contents to docs --- docs/language.md | 35 ++++++----- samples/000_Overview.noja | 71 ---------------------- samples/100_Expressions.noja | 112 ---------------------------------- samples/150_Branches.noja | 50 --------------- samples/200_Loops.noja | 39 ------------ samples/250_Functions.noja | 114 ----------------------------------- 6 files changed, 21 insertions(+), 400 deletions(-) delete mode 100644 samples/000_Overview.noja delete mode 100644 samples/100_Expressions.noja delete mode 100644 samples/150_Branches.noja delete mode 100644 samples/200_Loops.noja delete mode 100644 samples/250_Functions.noja 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'); - -# ------------------------------------------------------------------------- # -# ------------------------------------------------------------------------- # From fdc7e112c65f83bab77366fa76ab9d65dc0d33e7 Mon Sep 17 00:00:00 2001 From: cozis Date: Fri, 5 Nov 2021 17:01:34 +0100 Subject: [PATCH 04/11] added some docs --- docs/language.md | 27 ++++++---- src/compiler/compile.c | 15 ++++++ src/compiler/parse.c | 112 +++++++++++++++++++++++++++++++++++---- src/compiler/serialize.c | 17 ++++++ 4 files changed, 151 insertions(+), 20 deletions(-) diff --git a/docs/language.md b/docs/language.md index acad82a..e3acf5c 100644 --- a/docs/language.md +++ b/docs/language.md @@ -341,6 +341,13 @@ r = multiply(p, q); print(p, ' * ', q, ' = ', r, '\n'); ``` +If the function doesn't return any values, then the `none` value is returned. +As an example, the `print` function always returns `none` + +```py +print(print()); # none +``` + 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 @@ -354,24 +361,26 @@ these variables but the effect only lasts for the lifetame of the context local to the assignment. ```py -# Overwrite the print variable inside the global scope.. +# Overwrite the print variable inside the global +# scope.. print = 5; +# The reference to the print function is lost +# withing this scope. + fun test() { - # Now call print from inside the function. + # If the previous assignment were to overwrite the + # print function globally, the next statement would + # fail because the value 5 isn't a function. But + # it doesn't fail! 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! +# We can take the reference to the print function +# by taking it from a function! fun get_print_back() return print; diff --git a/src/compiler/compile.c b/src/compiler/compile.c index 1581a4e..efc033d 100644 --- a/src/compiler/compile.c +++ b/src/compiler/compile.c @@ -1,3 +1,18 @@ + +/* -- WHAT IS THIS FILE? -- + * + * This file implements the routines that transform the AST + * into a list of bytecodes. The functionalities of this file + * are exposed through the `compile` function, that takes as + * input an `AST` and outputs an `Executable`. + * The function that does the heavy lifting is `emit_instr_for_node` + * which walks the tree and writes instructions to the `ExeBuilder`. + * Some semantic errors are catched at this phase, in which + * case, they are reported by filling out the `error` structure + * and aborting. + * + */ + #include #include #include diff --git a/src/compiler/parse.c b/src/compiler/parse.c index b2be4f1..36a846b 100644 --- a/src/compiler/parse.c +++ b/src/compiler/parse.c @@ -1,3 +1,29 @@ + +/* -- WHAT IS THIS FILE? -- + * + * This file implements the parser of the language, that transforms + * `Source` objects into `AST` objects. The functionalities of this + * file are exposed throigh the `parse` function. + * + * It's mainly composed by routines that can each parse specific + * parts of a noja source string. For example, `parse_expression` + * parses expressions and `parse_while_statement` parses while statements. + * These functions call each other recursively to parse the source + * and build the abstract syntax tree (AST) that can be then compiled + * into bytecode. If at any point the parsing fails because of an + * external or internal error, then the error is reported and the parsing + * is aborted. + * Since the nodes of the AST always have the same lifetime (they're + * allocated at the same time and die all together), the allocator + * scheme of choise is a bump-pointer allocator. This way each of the + * parsing routines can allocate memory if it need it but doesn't need + * to free it if an error occurres. + * The parsing routines don't operate directly on the source text, but + * on the tokenized version of it. Before parsing a linked list of + * tokens is produced through the `tokenize` function. + * + */ + #include #include #include @@ -81,21 +107,36 @@ static inline _Bool isoper(char c) c == '='; } -AST *parse(Source *src, BPAlloc *alloc, Error *error) +/* Symbol: tokenize + * + * Build a list of tokens that represents the + * provided source code. + * + * + * Arguments: + * + * src: The source code to be tokenized. + * alloc: The allocator that will contain all of the + * generated tokens. + * error: Error information structure that is filled out if + * an error occurres. + * + * None of the arguments are optional. + * + * + * Returns: + * A pointer to the first node of a linked list of tokens. + * If an error occurres, NULL is returned and the `error` + * structure is filled out. + * + */ +static Token *tokenize(Source *src, BPAlloc *alloc, Error *error) { - assert(src != NULL); - assert(alloc != NULL); - const char *str = Source_GetBody(src); int len = Source_GetSize(src); assert(str != NULL); assert(len >= 0); - AST *ast = BPAlloc_Malloc(alloc, sizeof(AST)); - - if(ast == NULL) - return NULL; - Token *head = NULL, *tail = NULL; int i = 0; @@ -343,9 +384,58 @@ AST *parse(Source *src, BPAlloc *alloc, Error *error) tail = tok; } + return head; +} + +/* Symbol: parse + * + * Build an AST that represents the provided source code. + * + * + * Arguments: + * + * src: The source code to be parsed. + * alloc: The allocator that will contain all of the garbage + * the function needs and the final AST. + * error: Error information structure that is filled out if + * an error occurres. + * + * None of the arguments are optional. + * + * + * Returns: + * + * A pointer to the generated AST object. The AST object and + * all of the stuff that's referenced by it will be stored + * onto the provided allocator, therefore the AST will have + * the same lifetime of the allocator. If an error occurres, + * NULL is returned and the `error` structure is filled out. + * + * Notes: + * The AST structure holds a weak reference to the source + * object, therefore it will be invalidated if the source + * is freed before the AST. + * + */ +AST *parse(Source *src, BPAlloc *alloc, Error *error) +{ + assert(src != NULL); + assert(alloc != NULL); + assert(error != NULL); + + AST *ast = BPAlloc_Malloc(alloc, sizeof(AST)); + + if(ast == NULL) + return NULL; + + Token *tokens = tokenize(src, alloc, error); + + if(tokens == NULL) + return NULL; + Context ctx; - ctx.src = str; - ctx.token = head; + ctx.src = Source_GetBody(src); + ctx.token = tokens; ctx.alloc = alloc; ctx.error = error; diff --git a/src/compiler/serialize.c b/src/compiler/serialize.c index 3c295b7..6bb3c2f 100644 --- a/src/compiler/serialize.c +++ b/src/compiler/serialize.c @@ -1,3 +1,20 @@ + +/* -- WHAT IS THIS FILE? -- + * + * This file implements the routines that serialize the AST + * into JSON format. The JSON manipulation is handled by the + * third party library xJSON (written by me, still). + * The serialization functionality is exposed through the + * `serialize` function, that takes as an `AST` as argument + * and outputs a string of valid JSON. Therefore the xJSON + * dependency isn't exposed to the caller and can be regarded + * as an implementation detail. + * The way the serialization occurres is by converting the + * AST's representation native to the compiler to one native + * to xJSON, an then calling xj_encode on the converted AST. + * + */ + #include #include #include "serialize.h" From 87d675c3b6f721d60400f1d743b85ad60e329dd5 Mon Sep 17 00:00:00 2001 From: cozis Date: Fri, 5 Nov 2021 19:26:39 +0100 Subject: [PATCH 05/11] more docs --- src/README.md | 19 +++++++++++++++++++ src/compiler/compile.c | 23 ++++++++++++++++++++++- 2 files changed, 41 insertions(+), 1 deletion(-) create mode 100644 src/README.md diff --git a/src/README.md b/src/README.md new file mode 100644 index 0000000..34daebe --- /dev/null +++ b/src/README.md @@ -0,0 +1,19 @@ + +The source files directly contained by this folder expose to the user through the command line the functionalities implemented inside the subfolders. + +The `main.c` function is the entry of the source and, based on the options provided by the user, executes, parses or disassembles the source code. Inside `debug.c` is implemented the callback that `main.c` provides to the runtime if the user asks for an execution in debug mode that makes it possible to expose the internal state of the interpreter during the execution. + +The `utils` folder contains the implementations of general purpose data structures and definitions that are useful through all of the codebase. Some of the more used data structures are: + +* `BPAlloc`: A bump-pointer allocator. +* `Error`: A structure useful to report errors to function callers. +* `Source`: A string object that is used in place of raw strings to move source code around. + +The `common` folder implements the `Executable` data structure, which contains the result of a source's compilation. It can be though about as an array of bytecode instructions that can be directly executed. + +The `compiler` folder implements the compiler of the interpreter. The main routine that is exported from here is `compile`, which transform a `Source` into an `Executable`. Other functions are exported like `serialize` that transforms an `AST` in JSON format. This subfolder is the only part of the codebase that should be able to access the `AST` nodes. + +The `objects` folder implements the object model. In the context of this language, an object is a virtual class that implements a given set of methods. This folder exports functions that transform "raw" data types into objects, functions that do the inverse transformation and functions that trigger the virtual methods. This folder also contains the implementation of the heap and the garbage collector that needs to be tightly coupled with the object model. + +The `runtime` folder implements the routines that execute the `Executable`s. It depends heavily on `objects`. + diff --git a/src/compiler/compile.c b/src/compiler/compile.c index efc033d..8c954eb 100644 --- a/src/compiler/compile.c +++ b/src/compiler/compile.c @@ -9,7 +9,8 @@ * which walks the tree and writes instructions to the `ExeBuilder`. * Some semantic errors are catched at this phase, in which * case, they are reported by filling out the `error` structure - * and aborting. + * and aborting. It's also possible that the compilation fails + * bacause of internal errors (which usually means "out of memory"). * */ @@ -505,6 +506,26 @@ static _Bool emit_instr_for_node(ExeBuilder *exeb, Node *node, Error *error) return 0; } +/* Symbol: compile + * + * Serializes an AST into bytecode format. + * + * + * Arguments: + * + * ast: The AST to be serialized. + * alloc: The allocator that will be used to get new + * memory. (optional) + * error: Error information structure that is filled out if + * an error occurres. + * + * + * Returns: + * A pointer to an `Executable` that is the object that + * contains the bytecode. If an error occurres, NULL is + * returned and the `error` structure is filled out. + * + */ Executable *compile(AST *ast, BPAlloc *alloc, Error *error) { assert(ast != NULL); From ffe133b44698614443665842daeef2c3987ffb13 Mon Sep 17 00:00:00 2001 From: cozis Date: Fri, 5 Nov 2021 21:15:00 +0100 Subject: [PATCH 06/11] even more docs --- src/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/README.md b/src/README.md index 34daebe..983fdc8 100644 --- a/src/README.md +++ b/src/README.md @@ -1,19 +1,19 @@ The source files directly contained by this folder expose to the user through the command line the functionalities implemented inside the subfolders. -The `main.c` function is the entry of the source and, based on the options provided by the user, executes, parses or disassembles the source code. Inside `debug.c` is implemented the callback that `main.c` provides to the runtime if the user asks for an execution in debug mode that makes it possible to expose the internal state of the interpreter during the execution. +The `main.c` file implements the entry of the program and, based on the options provided by the user, executes, parses or disassembles the noja source code. In `debug.c` is implemented the callback that `main.c` provides to the runtime if the user asks for an execution in debug mode that makes it possible to expose the internal state of the interpreter during the execution. -The `utils` folder contains the implementations of general purpose data structures and definitions that are useful through all of the codebase. Some of the more used data structures are: +The `utils` folder contains the implementations of general purpose data structures and definitions that are useful through all of the codebase. Some of the more used data structures implemented in it are: -* `BPAlloc`: A bump-pointer allocator. +* `BPAlloc`: A bump-pointer allocator. It's useful during the compilation phase because lots of small objects need to be allocated and then freed at the same time when the final executable is produced. * `Error`: A structure useful to report errors to function callers. * `Source`: A string object that is used in place of raw strings to move source code around. The `common` folder implements the `Executable` data structure, which contains the result of a source's compilation. It can be though about as an array of bytecode instructions that can be directly executed. -The `compiler` folder implements the compiler of the interpreter. The main routine that is exported from here is `compile`, which transform a `Source` into an `Executable`. Other functions are exported like `serialize` that transforms an `AST` in JSON format. This subfolder is the only part of the codebase that should be able to access the `AST` nodes. +The `compiler` folder implements the compiler of the interpreter. The main routine that is exported from here is `compile`, which transforms a `Source` into an `Executable`. Other functions are exported like `serialize` that transforms an `AST` to a JSON string. This subfolder is the only part of the codebase that should be able to access the `AST` nodes. The `objects` folder implements the object model. In the context of this language, an object is a virtual class that implements a given set of methods. This folder exports functions that transform "raw" data types into objects, functions that do the inverse transformation and functions that trigger the virtual methods. This folder also contains the implementation of the heap and the garbage collector that needs to be tightly coupled with the object model. -The `runtime` folder implements the routines that execute the `Executable`s. It depends heavily on `objects`. - +The `runtime` folder implements the routines that run the `Executable`. It depends heavily on `objects`. It basically iterates over the executable's bytecode and applies the described actions to the objects. +It also implements a couple of objects that can't be implemented inside `objects` because they depend on the runtime internals. \ No newline at end of file From f9f577caef252ce38069a2e3590ae02cf861e5ce Mon Sep 17 00:00:00 2001 From: cozis Date: Fri, 5 Nov 2021 21:19:01 +0100 Subject: [PATCH 07/11] modified doc formatting --- src/README.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/README.md b/src/README.md index 983fdc8..07b8739 100644 --- a/src/README.md +++ b/src/README.md @@ -1,19 +1,17 @@ -The source files directly contained by this folder expose to the user through the command line the functionalities implemented inside the subfolders. +The source files directly contained by this folder expose to the user through the command line the functionalities implemented inside the subfolders. The `main.c` file implements the entry of the program and, based on the options provided by the user, executes, parses or disassembles the noja source code. In `debug.c` is implemented the callback that `main.c` provides to the runtime if the user asks for an execution in debug mode that makes it possible to expose the internal state of the interpreter during the execution. -The `main.c` file implements the entry of the program and, based on the options provided by the user, executes, parses or disassembles the noja source code. In `debug.c` is implemented the callback that `main.c` provides to the runtime if the user asks for an execution in debug mode that makes it possible to expose the internal state of the interpreter during the execution. +* The `utils` folder contains the implementations of general purpose data structures and definitions that are useful through all of the codebase. Some of the more used data structures implemented in it are: -The `utils` folder contains the implementations of general purpose data structures and definitions that are useful through all of the codebase. Some of the more used data structures implemented in it are: + * `BPAlloc`: A bump-pointer allocator. It's useful during the compilation phase because lots of small objects need to be allocated and then freed at the same time when the final executable is produced. + * `Error`: A structure useful to report errors to function callers. + * `Source`: A string object that is used in place of raw strings to move source code around. -* `BPAlloc`: A bump-pointer allocator. It's useful during the compilation phase because lots of small objects need to be allocated and then freed at the same time when the final executable is produced. -* `Error`: A structure useful to report errors to function callers. -* `Source`: A string object that is used in place of raw strings to move source code around. +* The `common` folder implements the `Executable` data structure, which contains the result of a source's compilation. It can be though about as an array of bytecode instructions that can be directly executed. -The `common` folder implements the `Executable` data structure, which contains the result of a source's compilation. It can be though about as an array of bytecode instructions that can be directly executed. +* The `compiler` folder implements the compiler of the interpreter. The main routine that is exported from here is `compile`, which transforms a `Source` into an `Executable`. Other functions are exported like `serialize` that transforms an `AST` to a JSON string. This subfolder is the only part of the codebase that should be able to access the `AST` nodes. -The `compiler` folder implements the compiler of the interpreter. The main routine that is exported from here is `compile`, which transforms a `Source` into an `Executable`. Other functions are exported like `serialize` that transforms an `AST` to a JSON string. This subfolder is the only part of the codebase that should be able to access the `AST` nodes. +* The `objects` folder implements the object model. In the context of this language, an object is a virtual class that implements a given set of methods. This folder exports functions that transform "raw" data types into objects, functions that do the inverse transformation and functions that trigger the virtual methods. This folder also contains the implementation of the heap and the garbage collector that needs to be tightly coupled with the object model. -The `objects` folder implements the object model. In the context of this language, an object is a virtual class that implements a given set of methods. This folder exports functions that transform "raw" data types into objects, functions that do the inverse transformation and functions that trigger the virtual methods. This folder also contains the implementation of the heap and the garbage collector that needs to be tightly coupled with the object model. - -The `runtime` folder implements the routines that run the `Executable`. It depends heavily on `objects`. It basically iterates over the executable's bytecode and applies the described actions to the objects. +* The `runtime` folder implements the routines that run the `Executable`. It depends heavily on `objects`. It basically iterates over the executable's bytecode and applies the described actions to the objects. It also implements a couple of objects that can't be implemented inside `objects` because they depend on the runtime internals. \ No newline at end of file From 68f4178980dcf02ad78df07ae55857094e47a74e Mon Sep 17 00:00:00 2001 From: cozis Date: Sat, 6 Nov 2021 14:00:41 +0100 Subject: [PATCH 08/11] bug fix --- samples/loop.noja | 2 ++ src/compiler/compile.c | 30 +++++++++++++----------- src/compiler/parse.c | 50 +++++++++++++++++++++------------------- src/compiler/serialize.c | 32 +++++++++++++------------ src/objects/o_map.c | 4 ---- src/runtime/builtins.c | 2 ++ src/runtime/o_nfunc.c | 3 ++- 7 files changed, 65 insertions(+), 58 deletions(-) diff --git a/samples/loop.noja b/samples/loop.noja index 2d18732..28760f2 100644 --- a/samples/loop.noja +++ b/samples/loop.noja @@ -24,3 +24,5 @@ while i < 3; # ------------------------------------- # # ------------------------------------- # + +print(count()); \ No newline at end of file diff --git a/src/compiler/compile.c b/src/compiler/compile.c index 8c954eb..b28f23d 100644 --- a/src/compiler/compile.c +++ b/src/compiler/compile.c @@ -1,18 +1,20 @@ -/* -- WHAT IS THIS FILE? -- - * - * This file implements the routines that transform the AST - * into a list of bytecodes. The functionalities of this file - * are exposed through the `compile` function, that takes as - * input an `AST` and outputs an `Executable`. - * The function that does the heavy lifting is `emit_instr_for_node` - * which walks the tree and writes instructions to the `ExeBuilder`. - * Some semantic errors are catched at this phase, in which - * case, they are reported by filling out the `error` structure - * and aborting. It's also possible that the compilation fails - * bacause of internal errors (which usually means "out of memory"). - * - */ +/* WHAT IS THIS FILE? +** +** This file implements the routines that transform the AST +** into a list of bytecodes. The functionalities of this file +** are exposed through the `compile` function, that takes as +** input an `AST` and outputs an `Executable`. +** +** The function that does the heavy lifting is `emit_instr_for_node` +** which walks the tree and writes instructions to the `ExeBuilder`. +** +** Some semantic errors are catched at this phase, in which +** case, they are reported by filling out the `error` structure +** and aborting. It's also possible that the compilation fails +** bacause of internal errors (which usually means "out of memory"). +** +*/ #include #include diff --git a/src/compiler/parse.c b/src/compiler/parse.c index 36a846b..a3cb1be 100644 --- a/src/compiler/parse.c +++ b/src/compiler/parse.c @@ -1,28 +1,30 @@ -/* -- WHAT IS THIS FILE? -- - * - * This file implements the parser of the language, that transforms - * `Source` objects into `AST` objects. The functionalities of this - * file are exposed throigh the `parse` function. - * - * It's mainly composed by routines that can each parse specific - * parts of a noja source string. For example, `parse_expression` - * parses expressions and `parse_while_statement` parses while statements. - * These functions call each other recursively to parse the source - * and build the abstract syntax tree (AST) that can be then compiled - * into bytecode. If at any point the parsing fails because of an - * external or internal error, then the error is reported and the parsing - * is aborted. - * Since the nodes of the AST always have the same lifetime (they're - * allocated at the same time and die all together), the allocator - * scheme of choise is a bump-pointer allocator. This way each of the - * parsing routines can allocate memory if it need it but doesn't need - * to free it if an error occurres. - * The parsing routines don't operate directly on the source text, but - * on the tokenized version of it. Before parsing a linked list of - * tokens is produced through the `tokenize` function. - * - */ +/* WHAT IS THIS FILE? +** +** This file implements the parser of the language, that transforms +** `Source` objects into `AST` objects. The functionalities of this +** file are exposed throigh the `parse` function. +** +** It's mainly composed by routines that can each parse specific +** parts of a noja source string. For example, `parse_expression` +** parses expressions and `parse_while_statement` parses while statements. +** These functions call each other recursively to parse the source +** and build the abstract syntax tree (AST) that can be then compiled +** into bytecode. If at any point the parsing fails because of an +** external or internal error, then the error is reported and the parsing +** is aborted. +** +** Since the nodes of the AST always have the same lifetime (they're +** allocated at the same time and die all together), the allocator +** scheme of choise is a bump-pointer allocator. This way each of the +** parsing routines can allocate memory if it need it but doesn't need +** to free it if an error occurres. +** +** The parsing routines don't operate directly on the source text, but +** on the tokenized version of it. Before parsing a linked list of +** tokens is produced through the `tokenize` function. +** +*/ #include #include diff --git a/src/compiler/serialize.c b/src/compiler/serialize.c index 6bb3c2f..8f339c7 100644 --- a/src/compiler/serialize.c +++ b/src/compiler/serialize.c @@ -1,19 +1,21 @@ -/* -- WHAT IS THIS FILE? -- - * - * This file implements the routines that serialize the AST - * into JSON format. The JSON manipulation is handled by the - * third party library xJSON (written by me, still). - * The serialization functionality is exposed through the - * `serialize` function, that takes as an `AST` as argument - * and outputs a string of valid JSON. Therefore the xJSON - * dependency isn't exposed to the caller and can be regarded - * as an implementation detail. - * The way the serialization occurres is by converting the - * AST's representation native to the compiler to one native - * to xJSON, an then calling xj_encode on the converted AST. - * - */ +/* WHAT IS THIS FILE? +** +** This file implements the routines that serialize the AST +** into JSON format. The JSON manipulation is handled by the +** third party library xJSON (written by me, still). +** +** The serialization functionality is exposed through the +** `serialize` function, that takes as an `AST` as argument +** and outputs a string of valid JSON. Therefore the xJSON +** dependency isn't exposed to the caller and can be regarded +** as an implementation detail. +** +** The way the serialization occurres is by converting the +** AST's representation native to the compiler to one native +** to xJSON, an then calling xj_encode on the converted AST. +** +*/ #include #include diff --git a/src/objects/o_map.c b/src/objects/o_map.c index 0377ddb..03a6d9c 100644 --- a/src/objects/o_map.c +++ b/src/objects/o_map.c @@ -116,8 +116,6 @@ static Object *select(Object *self, Object *key, Heap *heap, Error *error) // Not the one we wanted. } - int old_i = i; - pert >>= 5; i = (i * 5 + pert + 1) & mask; } @@ -249,8 +247,6 @@ static _Bool insert(Object *self, Object *key, Object *val, Heap *heap, Error *e // Collision. } - int old_i = i; - pert >>= 5; i = (i * 5 + pert + 1) & mask; } diff --git a/src/runtime/builtins.c b/src/runtime/builtins.c index 30a3fb0..857131d 100644 --- a/src/runtime/builtins.c +++ b/src/runtime/builtins.c @@ -11,6 +11,8 @@ static Object *bin_print(Runtime *runtime, Object **argv, unsigned int argc, Err static Object *bin_count(Runtime *runtime, Object **argv, unsigned int argc, Error *error) { + assert(argc == 1); + int n = Object_Count(argv[0], error); if(error->occurred) diff --git a/src/runtime/o_nfunc.c b/src/runtime/o_nfunc.c index 18f591a..cfad689 100644 --- a/src/runtime/o_nfunc.c +++ b/src/runtime/o_nfunc.c @@ -55,7 +55,8 @@ static Object *call(Object *self, Object **argv, unsigned int argc, Heap *heap, { // Some arguments are missing. argv2 = malloc(sizeof(Object*) * expected_argc); - + argc2 = expected_argc; + if(argv2 == NULL) { Error_Report(error, 1, "No memory"); From 78d848b22de43f77178634242a69e3ab7911abbe Mon Sep 17 00:00:00 2001 From: cozis Date: Sat, 6 Nov 2021 14:07:20 +0100 Subject: [PATCH 09/11] reorganized docs --- README.md | 72 ++++++++++++++++++++++++++++++++++++++++++++++ docs/language.md | 73 ----------------------------------------------- samples/loop.noja | 2 +- 3 files changed, 73 insertions(+), 74 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..bd10d41 --- /dev/null +++ b/README.md @@ -0,0 +1,72 @@ +# The Noja language + +## 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 overview + + 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 + +```py +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. \ No newline at end of file diff --git a/docs/language.md b/docs/language.md index e3acf5c..f721295 100644 --- a/docs/language.md +++ b/docs/language.md @@ -2,85 +2,12 @@ # 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 - - 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 overview - - 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 - -```py -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. - ## The first program The sintax is similar to Python's but is more C-like. A Noja script diff --git a/samples/loop.noja b/samples/loop.noja index 28760f2..ed84305 100644 --- a/samples/loop.noja +++ b/samples/loop.noja @@ -25,4 +25,4 @@ while i < 3; # ------------------------------------- # # ------------------------------------- # -print(count()); \ No newline at end of file +print(count(1, 2)); \ No newline at end of file From 368d0a467830616bc669ce2737d77b38217475fa Mon Sep 17 00:00:00 2001 From: cozis Date: Sat, 6 Nov 2021 14:30:09 +0100 Subject: [PATCH 10/11] reorganized docs --- README.md | 60 ++++-------------------------------------------- docs/language.md | 2 ++ 2 files changed, 6 insertions(+), 56 deletions(-) diff --git a/README.md b/README.md index bd10d41..28481d1 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,12 @@ ## 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. +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. ## Implementation overview - 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: +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. @@ -27,46 +17,4 @@ does things like: - referring to instructions by their index. -For example, by compiling the following snippet - -```py -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. \ No newline at end of file +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/docs/language.md b/docs/language.md index f721295..d857006 100644 --- a/docs/language.md +++ b/docs/language.md @@ -1,6 +1,8 @@ # The Noja language +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. + ## Table of contents 3. [The first program](#the-first-program) 4. [Expressions](#expressions) From 7b5301ff3be835f8064628c85ef2f2f31ac4b001 Mon Sep 17 00:00:00 2001 From: cozis Date: Sat, 6 Nov 2021 14:55:12 +0100 Subject: [PATCH 11/11] minor readme change --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 28481d1..8185d88 100644 --- a/README.md +++ b/README.md @@ -17,4 +17,5 @@ format and executing it. The bytecode is very high level since it does things li - referring to instructions by their index. -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. +All values (objects) are allocated on a garbage-collected heap. All variables are simply references to these objects. The garbage collection algorithm is a copy-and-compact. 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. +