From 9458632b270ca172554e9463b75c734eb36f64aa Mon Sep 17 00:00:00 2001 From: Francesco Cozzuto Date: Fri, 5 Nov 2021 00:10:45 +0100 Subject: [PATCH] fixed bug and added a little documentation --- samples/000_Overview.noja | 72 +++++++++++++++++++++++++++ samples/100_Expressions.noja | 94 ++++++++++++++++++++++++++++++++++++ samples/150_Branches.noja | 38 +++++++++++++++ samples/200_Loops.noja | 38 +++++++++++++++ samples/if-else.noja | 20 +++++++- samples/if-else2.noja | 4 +- samples/loop.noja | 29 +++++------ src/compiler/parse.c | 23 +++++++-- 8 files changed, 298 insertions(+), 20 deletions(-) create mode 100644 samples/000_Overview.noja create mode 100644 samples/100_Expressions.noja create mode 100644 samples/150_Branches.noja create mode 100644 samples/200_Loops.noja diff --git a/samples/000_Overview.noja b/samples/000_Overview.noja new file mode 100644 index 0000000..33651fd --- /dev/null +++ b/samples/000_Overview.noja @@ -0,0 +1,72 @@ + +# ------------------------------------------------------------------------- # +# --- Introduction -------------------------------------------------------- # +# +# This language was written as a personal study of how interpreters +# and compilers work. For this reason, the language is very basic and +# not innovative. +# One of the main inspirations was the CPython's source code since +# it's extremely readable and has a very simple 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. +# +# ------------------------------------------------------------------------- # +# ------------------------------------------------------------------------- # \ No newline at end of file diff --git a/samples/100_Expressions.noja b/samples/100_Expressions.noja new file mode 100644 index 0000000..701b071 --- /dev/null +++ b/samples/100_Expressions.noja @@ -0,0 +1,94 @@ + +# ------------------------------------------------------------------------- # +# --- 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: +# +# - function declaractions +# - expressions +# - if-else branches +# - while loops +# - do-while loops +# - return statements +# - composit statements +# +# 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. +# +# ------------------------------------------------------------------------- # +# ------------------------------------------------------------------------- # \ No newline at end of file diff --git a/samples/150_Branches.noja b/samples/150_Branches.noja new file mode 100644 index 0000000..e32416d --- /dev/null +++ b/samples/150_Branches.noja @@ -0,0 +1,38 @@ + +# ------------------------------------------------------------------------- # +# --- 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'); + } +# +# ------------------------------------------------------------------------- # +# ------------------------------------------------------------------------- # \ No newline at end of file diff --git a/samples/200_Loops.noja b/samples/200_Loops.noja new file mode 100644 index 0000000..4ea164a --- /dev/null +++ b/samples/200_Loops.noja @@ -0,0 +1,38 @@ + + +# ------------------------------------------------------------------------- # +# --- 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; + +# +# ------------------------------------------------------------------------- # +# ------------------------------------------------------------------------- # \ No newline at end of file diff --git a/samples/if-else.noja b/samples/if-else.noja index 6033fae..5a6a7b4 100644 --- a/samples/if-else.noja +++ b/samples/if-else.noja @@ -1,5 +1,23 @@ -if 'hello' == 'hello': +a = 'Francesco'; +b = 'Gennaro'; + +print('Are ', a, ' and ', b, ' the same name?\n'); + +if a == b: + print('Yup!\n'); +else + print('Nop!\n'); + + +# Now they are! + +a = 'Francesco'; +b = 'Francesco'; + +print('Are ', a, ' and ', b, ' the same name?\n'); + +if a == b: print('Yup!\n'); else print('Nop!\n'); \ No newline at end of file diff --git a/samples/if-else2.noja b/samples/if-else2.noja index b4b3045..888e0d8 100644 --- a/samples/if-else2.noja +++ b/samples/if-else2.noja @@ -1,5 +1,7 @@ -if false: +define = true; + +if define: a = 33; print(a, '\n'); \ No newline at end of file diff --git a/samples/loop.noja b/samples/loop.noja index ba0f2ee..2d18732 100644 --- a/samples/loop.noja +++ b/samples/loop.noja @@ -1,25 +1,26 @@ -p = true; +# ------------------------------------- # +# -- While loop ----------------------- # -while p: +i = 0; + +while i < 3: { - print('Hello!\n'); - p = false; + print('Hello from the while loop!\n'); + i = i + 1; } # ------------------------------------- # +# -- Do-Wile loop --------------------- # + +i = 0; do { - print('Hello 2!\n'); - } -while false; - -# ------------------------------------- # - -i = 0; -while i < 10: - { - print('i = ', i+1, '\n'); + print('Hello from the do-while loop!\n'); i = i + 1; } +while i < 3; + +# ------------------------------------- # +# ------------------------------------- # diff --git a/src/compiler/parse.c b/src/compiler/parse.c index cb6c50c..b2be4f1 100644 --- a/src/compiler/parse.c +++ b/src/compiler/parse.c @@ -209,8 +209,20 @@ AST *parse(Source *src, BPAlloc *alloc, Error *error) i += 1; // Skip the starting quote. - while(i < len && str[i] != f) - i += 1; + while(1) + { + while(i < len && str[i] != '\\' && str[i] != f) + i += 1; + + if(str[i] == '\\') + { + i += 1; // Consume the \. + + if(i < len && (str[i] == '\'' || str[i] == '"')) + i += 1; + } + else break; + } if(i == len) { @@ -577,7 +589,7 @@ static Node *parse_string_primary_expression(Context *ctx) if(src[i] == '\\') { i += 1; // Consume the \. - + if(temp_used + 1 >= (int) sizeof(temp)) { Error_Report(ctx->error, 1, "String is too big to be rendered inside the fixed size buffer"); @@ -593,10 +605,13 @@ static Node *parse_string_primary_expression(Context *ctx) { switch(src[i]) { + case '"': temp[temp_used++] = '"'; break; case 'n': temp[temp_used++] = '\n'; break; case 't': temp[temp_used++] = '\t'; break; case 'r': temp[temp_used++] = '\r'; break; - + case '\\': temp[temp_used++] = '\\'; break; + case '\'': temp[temp_used++] = '\''; break; + default: Error_Report(ctx->error, 0, "Invalid escape sequence \\%c", src[i]); return NULL;