ps4: init

This commit is contained in:
2026-03-09 11:20:07 +01:00
parent 0d46dc0f56
commit cfaeb2808d
32 changed files with 1804 additions and 0 deletions

13
ps4/.gitignore vendored Normal file
View File

@@ -0,0 +1,13 @@
.DS_Store
.idea
*.log
tmp/
build/
*.ast
*.svg
*.symbols
*.S
*.out
!vsl_programs/*/suggested/*

52
ps4/CMakeLists.txt Normal file
View File

@@ -0,0 +1,52 @@
cmake_minimum_required(VERSION 3.21)
project(vslc VERSION 1.0 LANGUAGES C)
set(VSLC_SOURCES "src/vslc.c"
"src/tree.c"
"src/graphviz_output.c"
"src/symbols.c"
"src/symbol_table.c")
set(VSLC_LEXER_SOURCE "src/scanner.l")
set(VSLC_PARSER_SOURCE "src/parser.y")
# === Setup generation of parser and scanner .c files and support headers
find_package(FLEX 2.6 REQUIRED)
find_package(BISON 3.5 REQUIRED)
# It is highly recommended to have bison v. 3.8 or later
# This version added the very useful counterexample-feature
if(BISON_VERSION VERSION_GREATER_EQUAL 3.8)
set(BISON_FLAGS -Wcounterexamples)
endif()
set(GEN_DIR "${CMAKE_CURRENT_BINARY_DIR}")
set(SCANNER_GEN_C "${GEN_DIR}/scanner.c")
set(PARSER_GEN_C "${GEN_DIR}/parser.c")
flex_target(scanner "${VSLC_LEXER_SOURCE}" "${SCANNER_GEN_C}" DEFINES_FILE "${GEN_DIR}/scanner.h")
bison_target(parser "${VSLC_PARSER_SOURCE}" "${PARSER_GEN_C}" DEFINES_FILE "${GEN_DIR}/parser.h"
COMPILE_FLAGS ${BISON_FLAGS})
add_flex_bison_dependency(scanner parser)
# === Finally declare the compiler target, depending on all .c files in the project ===
add_executable(vslc "${VSLC_SOURCES}" "${SCANNER_GEN_C}" "${PARSER_GEN_C}")
# Set some flags specifically for flex/bison
target_include_directories(vslc PRIVATE src "${GEN_DIR}")
target_compile_definitions(vslc PRIVATE "YYSTYPE=node_t *")
# Set general compiler flags, such as getting strdup from posix
target_compile_options(vslc PRIVATE -std=c17 -D_POSIX_C_SOURCE=200809L -Wall -g)
# === If Address Sanitizer is enabled, add the compiler and linker flag ===
# Enable ASan by invoking:
# cmake -B build -DUSE_ADDRESS_SANITIZER=ON
set (USE_ADDRESS_SANITIZER OFF CACHE BOOL "Should the Address Sanitizer tool be enabled?")
if (USE_ADDRESS_SANITIZER)
target_compile_options(vslc PRIVATE -fsanitize=address)
target_link_options(vslc PRIVATE -fsanitize=address)
endif()

71
ps4/src/graphviz_output.c Normal file
View File

@@ -0,0 +1,71 @@
#include "vslc.h"
// Helper function for escaping special characters when printing GraphViz strings
static void print_escaped_string(char* str)
{
for (char* c = str; *c != '\0'; c++)
{
switch (*c)
{
case '\\':
printf("\\\\");
break;
case '"':
printf("\\\"");
break;
case '\n':
printf("\\\\n");
break;
default:
putchar(*c);
break;
}
}
}
// A recursive function for printing a node as GraphViz, and all its children
static void graphviz_node_print_internal(node_t* node)
{
printf("node%p [label=\"%s", node, NODE_TYPE_NAMES[node->type]);
switch (node->type)
{
case OPERATOR:
printf("\\n%s", node->data.operator);
break;
case IDENTIFIER:
printf("\\n%s", node->data.identifier);
break;
case NUMBER_LITERAL:
printf("\\n%ld", node->data.number_literal);
break;
case STRING_LITERAL:
printf("\\n");
print_escaped_string(node->data.string_literal);
break;
case STRING_LIST_REFERENCE:
printf("\\n%zu", node->data.string_list_index);
break;
default:
break;
}
printf("\"];\n");
for (size_t i = 0; i < node->n_children; i++)
{
node_t* child = node->children[i];
if (child == NULL)
printf("node%p -- node%pNULL%zu ;\n", node, node, i);
else
{
printf("node%p -- node%p ;\n", node, child);
graphviz_node_print_internal(child);
}
}
}
void graphviz_node_print(node_t* root)
{
printf("graph \"\" {\n node[shape=box];\n");
graphviz_node_print_internal(root);
printf("}\n");
}

33
ps4/src/nodetypes.h Normal file
View File

@@ -0,0 +1,33 @@
// This is a special file that is not intended to be #include-d normally.
// Instead, it is included by "tree.h" and "tree.c" to provide both an enum of node types,
// and an array of strings containing the node names.
// clang-format off
#ifndef NODE_TYPE
#error The file nodetypes.h should only be included after defining the NODE_TYPE macro
#endif
NODE_TYPE(LIST),
NODE_TYPE(GLOBAL_VARIABLE_DECLARATION),
NODE_TYPE(ARRAY_INDEXING),
NODE_TYPE(VARIABLE),
NODE_TYPE(FUNCTION),
NODE_TYPE(BLOCK),
NODE_TYPE(LOCAL_VARIABLE_DECLARATION),
NODE_TYPE(LOCAL_VARIABLE),
NODE_TYPE(ASSIGNMENT_STATEMENT),
NODE_TYPE(RETURN_STATEMENT),
NODE_TYPE(PRINT_STATEMENT),
NODE_TYPE(PRINTLN_STATEMENT),
NODE_TYPE(IF_STATEMENT),
NODE_TYPE(WHILE_STATEMENT),
NODE_TYPE(BREAK_STATEMENT),
NODE_TYPE(FUNCTION_CALL),
NODE_TYPE(OPERATOR), // uses the data field "operator"
NODE_TYPE(IDENTIFIER), // uses and owns the data field "identifer"
NODE_TYPE(NUMBER_LITERAL), // uses the data field "number_literal"
NODE_TYPE(STRING_LITERAL), // uses and owns the data field "string_literal"
NODE_TYPE(STRING_LIST_REFERENCE), // uses the data field "string_list_index"
#undef NODE_TYPE

262
ps4/src/parser.y Normal file
View File

@@ -0,0 +1,262 @@
%{
#include "vslc.h"
// State variables from the flex generated scanner
extern int yylineno; // The line currently being read
extern char yytext[]; // The text of the last consumed lexeme
// The main flex driver function used by the parser
int yylex(void);
// The function called by the parser when errors occur
int yyerror(const char *error)
{
fprintf(stderr, "%s on line %d\n", error, yylineno);
exit(EXIT_FAILURE);
}
// Helper macros for creating nodes
#define N0C(type) \
node_create( (type), 0 )
#define N1C(type, child0) \
node_create( (type), 1, (child0) )
#define N2C(type, child0, child1) \
node_create( (type), 2, (child0), (child1) )
#define N3C(type, child0, child1, child2) \
node_create( (type), 3, (child0), (child1), (child2) )
%}
%token FUNC VAR RETURN PRINT PRINTLN IF ELSE WHILE BREAK AND OR
%token NUMBER_TOKEN IDENTIFIER_TOKEN STRING_TOKEN
// Use operator precedence to ensure order of operations is correct
%left '?' ':' // The ternary ? : operator has the lowest precedence of them all
%left OR // Or has lower precedence than and, just like in C
%left AND
%left '=' '!' // == and !=
%left '<' '>' // < <= > and >=
%left '+' '-'
%left '*' '/'
%right UNARY_OPERATORS
// Resolve the nested if-if-else ambiguity with precedence
%nonassoc ')'
%nonassoc ELSE
%%
program :
global_list { root = $1; }
;
global_list :
global { $$ = N1C(LIST, $1); }
| global_list global { $$ = append_to_list_node($1, $2); }
;
global :
function { $$ = $1; }
| global_variable_declaration { $$ = $1; }
;
global_variable_declaration :
VAR global_variable_list { $$ = N1C(GLOBAL_VARIABLE_DECLARATION, $2); }
;
global_variable_list :
global_variable { $$ = N1C(LIST, $1); }
| global_variable_list ',' global_variable { $$ = append_to_list_node($1, $3); }
;
global_variable :
identifier { $$ = $1; }
| array_indexing { $$ = $1; }
;
array_indexing:
identifier '[' expression ']' { $$ = N2C(ARRAY_INDEXING, $1, $3); }
;
parameter_list :
/* epsilon */ { $$ = N0C(LIST); }
| identifier { $$ = N1C(LIST, $1); }
| parameter_list ',' identifier { $$ = append_to_list_node($1, $3); }
;
function :
FUNC identifier '(' parameter_list ')' statement
{ $$ = N3C(FUNCTION, $2, $4, $6); }
;
statement :
block { $$ = $1; }
| assignment_statement { $$ = $1; }
| return_statement { $$ = $1; }
| print_statement { $$ = $1; }
| println_statement { $$ = $1; }
| if_statement { $$ = $1; }
| while_statement { $$ = $1; }
| break_statement { $$ = $1; }
| function_call { $$ = $1; }
;
block :
'{' statement_or_declaration_list '}' { $$ = N1C(BLOCK, $2); }
;
statement_or_declaration_list :
/* epsilon */ { $$ = N0C(LIST); }
| statement_or_declaration_list statement_or_declaration { $$ = append_to_list_node($1, $2); }
;
statement_or_declaration :
statement { $$ = $1; }
| local_variable_declaration { $$ = $1; }
;
local_variable_declaration :
VAR local_variable_list { $$ = N1C(LOCAL_VARIABLE_DECLARATION, $2); }
;
local_variable_list :
local_variable { $$ = N1C(LIST, $1); }
| local_variable_list ',' local_variable { $$ = append_to_list_node($1, $3); }
;
local_variable :
identifier { $$ = N1C(LOCAL_VARIABLE, $1); }
| identifier '=' expression { $$ = N2C(LOCAL_VARIABLE, $1, $3); }
;
assignment_statement :
identifier '=' expression { $$ = N2C(ASSIGNMENT_STATEMENT, $1, $3); }
| array_indexing '=' expression { $$ = N2C(ASSIGNMENT_STATEMENT, $1, $3); }
;
return_statement :
RETURN expression
{ $$ = N1C(RETURN_STATEMENT, $2); }
;
print_statement :
PRINT '(' print_list ')'
{ $$ = N1C(PRINT_STATEMENT, $3); }
;
println_statement :
PRINTLN '(' print_list ')'
{ $$ = N1C(PRINTLN_STATEMENT, $3); }
;
print_list :
print_item { $$ = N1C(LIST, $1); }
| print_list ',' print_item { $$ = append_to_list_node($1, $3); }
;
print_item :
expression { $$ = $1; }
| string { $$ = $1; }
;
break_statement :
BREAK { $$ = N0C(BREAK_STATEMENT); }
;
if_statement :
IF '(' expression ')' statement
{ $$ = N2C(IF_STATEMENT, $3, $5); }
| IF '(' expression ')' statement ELSE statement
{ $$ = N3C(IF_STATEMENT, $3, $5, $7); }
;
while_statement :
WHILE '(' expression ')' statement
{ $$ = N2C(WHILE_STATEMENT, $3, $5); }
;
expression :
expression '?' expression ':' expression
{
$$ = N3C(OPERATOR, $1, $3, $5);
$$->data.operator = "?:";
}
| expression OR expression
{
$$ = N2C(OPERATOR, $1, $3);
$$->data.operator = "or";
}
| expression AND expression
{
$$ = N2C(OPERATOR, $1, $3);
$$->data.operator = "and";
}
| expression '=' '=' expression
{
$$ = N2C(OPERATOR, $1, $4);
$$->data.operator = "==";
}
| expression '!' '=' expression
{
$$ = N2C(OPERATOR, $1, $4);
$$->data.operator = "!=";
}
| expression '<' expression
{
$$ = N2C(OPERATOR, $1, $3);
$$->data.operator = "<";
}
| expression '<' '=' expression
{
$$ = N2C(OPERATOR, $1, $4);
$$->data.operator = "<=";
}
| expression '>' expression
{
$$ = N2C(OPERATOR, $1, $3);
$$->data.operator = ">";
}
| expression '>' '=' expression
{
$$ = N2C(OPERATOR, $1, $4);
$$->data.operator = ">=";
}
| expression '+' expression
{
$$ = N2C(OPERATOR, $1, $3);
$$->data.operator = "+";
}
| expression '-' expression
{
$$ = N2C(OPERATOR, $1, $3);
$$->data.operator = "-";
}
| expression '*' expression
{
$$ = N2C(OPERATOR, $1, $3);
$$->data.operator = "*";
}
| expression '/' expression
{
$$ = N2C(OPERATOR, $1, $3);
$$->data.operator = "/";
}
| '-' expression %prec UNARY_OPERATORS
{
$$ = N1C(OPERATOR, $2);
$$->data.operator = "-";
}
| '!' expression %prec UNARY_OPERATORS
{
$$ = N1C(OPERATOR, $2);
$$->data.operator = "!";
}
| '(' expression ')' { $$ = $2; }
| number { $$ = $1; }
| identifier { $$ = $1; }
| array_indexing { $$ = $1; }
| function_call { $$ = $1; }
;
function_call :
identifier '(' argument_list ')' { $$ = N2C(FUNCTION_CALL, $1, $3); }
argument_list :
expression_list { $$ = $1; }
| /* epsilon */ { $$ = N0C(LIST); }
;
expression_list :
expression { $$ = N1C(LIST, $1); }
| expression_list ',' expression { $$ = append_to_list_node($1, $3); }
;
identifier :
IDENTIFIER_TOKEN
{
$$ = N0C(IDENTIFIER);
// Allocate a copy of yytext to keep in the syntax tree as data
$$->data.identifier = strdup(yytext);
}
number :
NUMBER_TOKEN
{
$$ = N0C(NUMBER_LITERAL);
$$->data.number_literal = strtol(yytext, NULL, 10);
}
string :
STRING_TOKEN
{
$$ = N0C(STRING_LITERAL);
$$->data.string_literal = strdup(yytext);
}
%%

38
ps4/src/scanner.l Normal file
View File

@@ -0,0 +1,38 @@
%{
#include "vslc.h"
// The tokens defined in parser.y
#include "parser.h"
// parser.h contains some unused functions, ignore that
#pragma GCC diagnostic ignored "-Wunused-function"
%}
%option noyywrap
%option array
%option yylineno
WHITESPACE [ \t\v\r\n]
COMMENT \/\/[^\n]+
QUOTED \"([^\"\n]|\\\")*\"
%%
{WHITESPACE}+ { /* Eliminate whitespace */ }
{COMMENT} { /* Eliminate comments */ }
func { return FUNC; }
var { return VAR; }
return { return RETURN; }
print { return PRINT; }
println { return PRINTLN; }
if { return IF; }
else { return ELSE; }
while { return WHILE; }
break { return BREAK; }
and { return AND; }
or { return OR; }
[0-9]+ { return NUMBER_TOKEN; }
[A-Za-z_][0-9A-Za-z_]* { return IDENTIFIER_TOKEN; }
{QUOTED} { return STRING_TOKEN; }
/* Unknown chars get returned as single char tokens */
. { return yytext[0]; }
%%

167
ps4/src/symbol_table.c Normal file
View File

@@ -0,0 +1,167 @@
#include "symbol_table.h"
#include "symbols.h"
#include <assert.h>
#include <stdlib.h>
#include <string.h>
static insert_result_t symbol_hashmap_insert(symbol_hashmap_t* hashmap, symbol_t* symbol);
// ================== Symbol table code =================
// Initializes a symboltable with 0 entries. Will be resized upon first insertion
symbol_table_t* symbol_table_init(void)
{
symbol_table_t* result = malloc(sizeof(symbol_table_t));
*result = (symbol_table_t){.symbols = NULL,
.n_symbols = 0,
.capacity = 0,
.hashmap = symbol_hashmap_init()};
return result;
}
// Adds a symbol to both the symbol table, and its hashmap (if possible)
insert_result_t symbol_table_insert(symbol_table_t* table, struct symbol* symbol)
{
// Inserts can fail, if the hashmap already contains the name
if (symbol_hashmap_insert(table->hashmap, symbol) == INSERT_COLLISION)
return INSERT_COLLISION;
// If the table is full, resize the list
if (table->n_symbols + 1 >= table->capacity)
{
table->capacity = table->capacity * 2 + 8;
table->symbols = realloc(table->symbols, table->capacity * sizeof(symbol_t*));
}
table->symbols[table->n_symbols] = symbol;
symbol->sequence_number = table->n_symbols;
table->n_symbols++;
return INSERT_OK;
}
// Destroys the given symbol table, its hashmap, and all the symbols it owns
void symbol_table_destroy(symbol_table_t* table)
{
for (int i = 0; i < table->n_symbols; i++)
free(table->symbols[i]);
free(table->symbols);
symbol_hashmap_destroy(table->hashmap);
free(table);
}
// ==================== Hashmap code ====================
// Initializes a hashmap with 0 buckets. Will be resized upon first insertion
symbol_hashmap_t* symbol_hashmap_init()
{
symbol_hashmap_t* result = malloc(sizeof(symbol_hashmap_t));
*result = (symbol_hashmap_t){.buckets = NULL, .n_buckets = 0, .n_entries = 0, .backup = NULL};
return result;
}
// Calculates a naive 64-bit hash of the given string
static uint64_t hash_string(const char* string)
{
assert(string != NULL);
uint64_t hash = 31;
for (const char* c = string; *c != '\0'; c++)
hash = hash * 257 + *c;
return hash;
}
// Allocates a larger list of buckets, and inserts all hashmap entries again
static void symbol_hashmap_resize(symbol_hashmap_t* hashmap, size_t new_capacity)
{
symbol_t** old_buckets = hashmap->buckets;
size_t old_capacity = hashmap->n_buckets;
// Use calloc, since it initalizes the memory to 0, aka NULL entries
hashmap->buckets = calloc(new_capacity, sizeof(symbol_t*));
hashmap->n_buckets = new_capacity;
hashmap->n_entries = 0;
// Now re-insert all entries from the old buckets
for (int i = 0; i < old_capacity; i++)
{
if (old_buckets[i] != NULL)
symbol_hashmap_insert(hashmap, old_buckets[i]);
}
free(old_buckets);
}
// Performs insertion into the hashmap.
// The hashmap uses open addressing, with up to one entry per bucket.
// If our first choice of bucket is full, we look at the next bucket, until we find room.
static insert_result_t symbol_hashmap_insert(symbol_hashmap_t* hashmap, symbol_t* symbol)
{
// Make sure that the fill ratio of the hashmap never exeeds 1/2
int new_size = hashmap->n_entries + 1;
if (new_size * 2 > hashmap->n_buckets)
symbol_hashmap_resize(hashmap, hashmap->n_buckets * 2 + 8);
// Now calculate the position of the new entry
uint64_t hash = hash_string(symbol->name);
size_t bucket = hash % hashmap->n_buckets;
// Iterate until we find an empty bucket
while (hashmap->buckets[bucket] != NULL)
{
// Check if the existing entry is a name collision
if (strcmp(hashmap->buckets[bucket]->name, symbol->name) == 0)
return INSERT_COLLISION; // An entry with the same name already exists
// Go to the next bucket
bucket = (bucket + 1) % hashmap->n_buckets;
}
// We found an emoty bucket, insert the symbol here
hashmap->buckets[bucket] = symbol;
hashmap->n_entries++;
return INSERT_OK; // We successfully inserted a new symbol
}
// Performs lookup in the hashmap.
// Hashes the given string, and checks if the resulting bucket contains the item.
// Since the hashmap uses open addressing, the entry can also be in the next bucket,
// so we iterate until we either find the item, or find an empty bucket.
//
// If the key isn't found in this hashmap, but we have a backup, lookup continues there.
// Otherwise, NULL is returned.
symbol_t* symbol_hashmap_lookup(symbol_hashmap_t* hashmap, const char* name)
{
uint64_t hash = hash_string(name);
// Loop through the linked list of hashmaps and backup hashmaps
while (hashmap != NULL)
{
// Skip any hashmaps with 0 buckets
if (hashmap->n_buckets == 0)
{
hashmap = hashmap->backup;
continue;
}
size_t bucket = hash % hashmap->n_buckets;
while (hashmap->buckets[bucket] != NULL)
{
// Check if the entry in the bucket has a matching name
if (strcmp(hashmap->buckets[bucket]->name, name) == 0)
return hashmap->buckets[bucket];
// Otherwise keep iterating until we find a hit, or an empty bucket
bucket = (bucket + 1) % hashmap->n_buckets;
}
// No entry with the required name existed in the hashmap, so go to the backup
hashmap = hashmap->backup;
}
// The entry was never found, and we are all out of backups
return NULL;
}
void symbol_hashmap_destroy(symbol_hashmap_t* hashmap)
{
free(hashmap->buckets);
free(hashmap);
}

64
ps4/src/symbol_table.h Normal file
View File

@@ -0,0 +1,64 @@
#ifndef SYMBOL_TABLE_H
#define SYMBOL_TABLE_H
#include "tree.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
// We use hashmaps to make lookups quick.
// The entries are symbols, using the name of the symbol as the key.
// The hashmap logic is already implemented in symbol_table.c
// NOTE that this hashmap does not support removing entries.
typedef struct symbol_hashmap
{
struct symbol** buckets; // A bucket may contain 0 or 1 entries
size_t n_buckets;
size_t n_entries;
// If a key is not found, the lookup function will consult this as a backup
struct symbol_hashmap* backup;
} symbol_hashmap_t;
// A dynamically sized list of symbols, including a hashmap for fast lookups
// The logic for the symbol table is already implemented in symbol_table.c
typedef struct symbol_table
{
struct symbol** symbols;
size_t n_symbols;
size_t capacity;
symbol_hashmap_t* hashmap;
} symbol_table_t;
typedef enum
{
INSERT_OK = 0,
INSERT_COLLISION = 1
} insert_result_t;
// Initializes a new, empty symbol table, including an empty hashmap
symbol_table_t* symbol_table_init(void);
// Tries to insert the given symbol into the symbol table.
// If the topmost hashmap already contains a symbol with the same name,
// INSERT_COLLISION is returned, otherwise the result is INSERT_OK.
//
// The symbol table takes ownership of the symbol, and assigns it a sequence number.
// DO NOT change the symbol's name after insertion.
insert_result_t symbol_table_insert(symbol_table_t* table, struct symbol* symbol);
// Destroys the given symbol table, its hashmap, and all the symbols it owns
void symbol_table_destroy(symbol_table_t* table);
// Initalizes a new, empty hashmap
symbol_hashmap_t* symbol_hashmap_init(void);
// Looks for a symbol in the symbol hashmap, matching the given name.
// If no symbol is found, the hashmap's backup hashmap is checked.
// If the name can't be found in the backup chain either, NULL is returned.
struct symbol* symbol_hashmap_lookup(symbol_hashmap_t* hashmap, const char* name);
// Frees the memory used by the hashmap
void symbol_hashmap_destroy(symbol_hashmap_t* hashmap);
#endif // SYMBOL_TABLE_H

163
ps4/src/symbols.c Normal file
View File

@@ -0,0 +1,163 @@
#include "vslc.h"
// Declaration of global symbol table
symbol_table_t* global_symbols;
// Declarations of helper functions defined further down in this file
static void find_globals(void);
static void bind_names(symbol_table_t* local_symbols, node_t* root);
static void print_symbol_table(symbol_table_t* table, int nesting);
static void destroy_symbol_tables(void);
static size_t add_string(char* string);
static void print_string_list(void);
static void destroy_string_list(void);
/* External interface */
// Creates a global symbol table, and local symbol tables for each function.
// All usages of symbols are bound to their symbol table entries.
// All strings are entered into the string_list
void create_tables(void)
{
// TODO:
// First use find_globals() to create the global symbol table.
// As global symbols are added, function symbols get their own local symbol tables as well.
//
// Once all global symbols are added, go through all functions bodies and bind references.
//
// Binding should performed by bind_names(function symbol table, function body AST node).
// IDENTIFIERs that reference declarations should point to the symbol_t they reference.
// It handles pushing and popping scopes, and adding variables to the local symbol table.
// A final task performed by bind_names(...), is adding strings to the global string list.
}
// Prints the global symbol table, and the local symbol tables for each function.
// Also prints the global string list.
// Finally prints out the AST again, with bound symbols.
void print_tables(void)
{
print_symbol_table(global_symbols, 0);
printf("\n == STRING LIST == \n");
print_string_list();
printf("\n == BOUND SYNTAX TREE == \n");
print_syntax_tree();
}
// Cleans up all memory owned by symbol tables and the global string list
void destroy_tables(void)
{
destroy_symbol_tables();
destroy_string_list();
}
/* Internal matters */
// Goes through all global declarations, adding them to the global symbol table.
// When adding functions, a local symbol table with symbols for its parameters are created.
static void find_globals(void)
{
global_symbols = symbol_table_init();
// TODO: Create symbols for all global defintions (global variables, arrays and functions),
// and add them to the global symbol table. See the symtype_t enum in "symbols.h"
// When creating a symbol for a function, also create a local symbol_table_t for it.
// Store this local symbol table in the function symbol's function_symtable field.
// Any parameters the function may have should be added to this local symbol table.
// TIP: create symbols using malloc(sizeof(symbol_t)), and assigning the relevant fields.
// Use symbol_table_insert() (from "symbol_table.h") to insert new symbols into tables.
// If a symbol already exists with the same name, the insertion will return INSERT_COLLISION.
// Feel free to print an error message and abort using exit(EXIT_FAILURE),
// but we will not be testing your compiler on invalid VSL.
}
// A recursive function that traverses the body of a function doing the following:
// - LOCAL_VARIABLE declarations have symbols created in the function's local symbol table.
// - BLOCKs push and pop lexical scopes for local variables.
// - All IDENTIFIER nodes that are not declarations are bound to the symbol it references.
// - STRING_LITERAL nodes get their data moved into the global string list.
// The node is replaced with a STRING_LIST_REFERENCE node,
// where the data.string_list_index field is set to the string's index in the string list.
static void bind_names(symbol_table_t* local_symbols, node_t* node)
{
// TODO: Implement bind_names, doing all the things described above
// Tip: See symbol_hashmap_init() in symbol_table.h, to make new hashmaps for new scopes.
// Remember the symbol_hashmap_t's backup pointer, forming a linked list of backup hashmaps.
// Can you use this linked list to implement a stack of hash maps?
// Tip: Strings can be added to the string list using add_string(). It returns its index.
// Note: If an IDENTIFIER has a name that does not correspond to any symbol in the current scope,
// a parent scope, or in the global symbol table, that is an error.
// Feel free to print a nice error message and abort.
// We will not test your compiler on incorrect VSL.
}
// Prints the given symbol table, with sequence number, symbol names and types.
// When printing function symbols, its local symbol table is recursively printed, with indentation.
static void print_symbol_table(symbol_table_t* table, int nesting)
{
for (size_t i = 0; i < table->n_symbols; i++)
{
symbol_t* symbol = table->symbols[i];
printf(
"%*s%ld: %s(%s)\n",
nesting * 4,
"",
symbol->sequence_number,
SYMBOL_TYPE_NAMES[symbol->type],
symbol->name);
// If the symbol is a function, print its local symbol table as well
if (symbol->type == SYMBOL_FUNCTION)
print_symbol_table(symbol->function_symtable, nesting + 1);
}
}
// Frees up the memory used by the global symbol table, all local symbol tables, and their symbols
static void destroy_symbol_tables(void)
{
// TODO: Implement cleanup. All symbols in the program are owned by exactly one symbol table.
// TIP: Using symbol_table_destroy() goes a long way, but it only cleans up the given table.
// Try cleaning up all local symbol tables before cleaning up the global one.
}
// Declaration of global string list
char** string_list;
size_t string_list_len;
static size_t string_list_capacity;
// Adds the given string to the global string list, resizing if needed.
// Takes ownership of the string, and returns its position in the string list.
static size_t add_string(char* string)
{
// TODO: Write a helper function you can use during bind_names(),
// to easily add a string into the dynamically growing string_list.
// The length of the string list should be stored in string_list_len.
// The variable string_list_capacity should contain the maximum number of char*
// that can fit in the current string_list before we need to allocate a larger array.
// If length is about to surpass capacity, create a larger allocation first.
// Tip: See the realloc function from the standard library
// Return the position the added string gets in the list.
}
// Prints all strings added to the global string list
static void print_string_list(void)
{
for (size_t i = 0; i < string_list_len; i++)
printf("%ld: %s\n", i, string_list[i]);
}
// Frees all strings in the global string list, and the string list itself
static void destroy_string_list(void)
{
// TODO: Called during cleanup, free strings, and the memory used by the string list itself
}

58
ps4/src/symbols.h Normal file
View File

@@ -0,0 +1,58 @@
#ifndef SYMBOLS_H
#define SYMBOLS_H
#include "symbol_table.h"
#include <stddef.h>
typedef enum
{
SYMBOL_GLOBAL_VAR,
SYMBOL_GLOBAL_ARRAY,
SYMBOL_FUNCTION,
SYMBOL_PARAMETER,
SYMBOL_LOCAL_VAR,
} symtype_t;
// Use as a normal array, to get the name of a symbol type: SYMBOL_TYPE_NAMES[symbol->type]
#define SYMBOL_TYPE_NAMES \
((const char*[]){[SYMBOL_GLOBAL_VAR] = "GLOBAL_VAR", \
[SYMBOL_GLOBAL_ARRAY] = "GLOBAL_ARRAY", \
[SYMBOL_FUNCTION] = "FUNCTION", \
[SYMBOL_PARAMETER] = "PARAMETER", \
[SYMBOL_LOCAL_VAR] = "LOCAL_VAR"})
// Struct representing the definition of a symbol
typedef struct symbol
{
char* name; // Symbol name ( not owned )
symtype_t type; // Symbol type
node_t* node; // The AST node that defined this symbol ( not owned )
size_t sequence_number; // Sequence number in the symbol table this symbol belongs to.
// assigned automatically upon insertion in a symbol table.
// Global variables and arrays have function_symtable = NULL
// Functions point to their own symbol tables here, but the function itself is a global symbol
// Parameters and local variables point to the symtable they belong to
struct symbol_table* function_symtable;
} symbol_t;
// Global symbol table, which contains and owns all global symbols.
// All function symbols in the global symbol table have pointers to their own local symbol table.
extern symbol_table_t* global_symbols;
// Global string list, owns all contained strings
extern char** string_list;
extern size_t string_list_len;
// Traverses the abstract syntax tree and creates symbol tables, both global and local.
// Places strings in the string_list, and turns STRING_LITERAL nodes into STRING_LIST_REFERENCEs.
void create_tables(void);
// Outputs all global and local symbol tables, and the string list.
// Lastly outputs the abstract syntax tree with references to symbols
void print_tables(void);
// Clean up all memory owned by symbol tables
void destroy_tables(void);
#endif // SYMBOLS_H

385
ps4/src/tree.c Normal file
View File

@@ -0,0 +1,385 @@
#include "vslc.h"
// Global root for abstract syntax tree
node_t* root;
// Declarations of helper functions defined further down in this file
static void node_print(node_t* node, int nesting);
static node_t* simplify_subtree(node_t* node);
static void node_finalize(node_t* discard);
static void destroy_subtree(node_t* discard);
// Initialize a node with the given type and children
node_t* node_create(node_type_t type, size_t n_children, ...)
{
node_t* result = malloc(sizeof(node_t));
// Initialize every field in the struct
*result = (node_t){
.type = type,
.n_children = n_children,
.children = malloc(n_children * sizeof(node_t*)),
.symbol = NULL,
};
// Read each child node from the va_list
va_list child_list;
va_start(child_list, n_children);
for (size_t i = 0; i < n_children; i++)
{
result->children[i] = va_arg(child_list, node_t*);
}
va_end(child_list);
return result;
}
// Append an element to the given LIST node, returns the list node
node_t* append_to_list_node(node_t* list_node, node_t* element)
{
assert(list_node->type == LIST);
// Calculate the minimum size of the new allocation
size_t min_allocation_size = list_node->n_children + 1;
// Round up to the next power of two
size_t new_allocation_size = 1;
while (new_allocation_size < min_allocation_size)
new_allocation_size *= 2;
// Resize the allocation
list_node->children = realloc(list_node->children, new_allocation_size * sizeof(node_t*));
// Insert the new element and increase child count by 1
list_node->children[list_node->n_children] = element;
list_node->n_children++;
return list_node;
}
// Outputs the entire syntax tree to the terminal
void print_syntax_tree(void)
{
// If the environment variable GRAPHVIZ_OUTPUT is set, print a GraphViz graph in the dot format
if (getenv("GRAPHVIZ_OUTPUT") != NULL)
graphviz_node_print(root);
else
node_print(root, 0);
}
// Performs constant folding and replaces nodes with simpler nodes
void simplify_syntax_tree(void)
{
root = simplify_subtree(root);
}
// Frees all memory held by the syntax tree
void destroy_syntax_tree(void)
{
destroy_subtree(root);
root = NULL;
}
// The rest of this file contains private helper functions used by the above functions
// Prints out the given node and all its children recursively
static void node_print(node_t* node, int nesting)
{
// Indent the line based on how deep the node is in the syntax tree
printf("%*s", nesting, "");
if (node == NULL)
{
printf("(NULL)\n");
return;
}
printf("%s", NODE_TYPE_NAMES[node->type]);
// For nodes with extra data, include it in the printout
switch (node->type)
{
case OPERATOR:
printf(" (%s)", node->data.operator);
break;
case IDENTIFIER:
printf(" (%s)", node->data.identifier);
break;
case NUMBER_LITERAL:
printf(" (%ld)", node->data.number_literal);
break;
case STRING_LITERAL:
printf(" (%s)", node->data.string_literal);
break;
case STRING_LIST_REFERENCE:
printf(" (%zu)", node->data.string_list_index);
break;
default:
break;
}
// If the node is a reference to a symbol, print its type and number
if (node->symbol)
{
printf(" %s(%zu)", SYMBOL_TYPE_NAMES[node->symbol->type], node->symbol->sequence_number);
}
putchar('\n');
// Recursively print children, with some more indentation
for (size_t i = 0; i < node->n_children; i++)
node_print(node->children[i], nesting + 1);
}
// If the given OPERATOR node is "and" or "or", converts it to a ternary ?: operator like so:
// a and b ===> a ? b : 0
// a or b ===> a ? 1 : b
static node_t* convert_operator(node_t* node)
{
assert(node->type == OPERATOR);
const char* op = node->data.operator;
if (strcmp(op, "and") == 0)
{
node_t* lhs = node->children[0];
node_t* rhs = node->children[1];
node_t* zero = node_create(NUMBER_LITERAL, 0);
zero->data.number_literal = 0;
node_t* ternary = node_create(OPERATOR, 3, lhs, rhs, zero);
ternary->data.operator= "?:";
node_finalize(node);
return ternary;
}
if (strcmp(op, "or") == 0)
{
node_t* lhs = node->children[0];
node_t* rhs = node->children[1];
node_t* one = node_create(NUMBER_LITERAL, 0);
one->data.number_literal = 1;
node_t* ternary = node_create(OPERATOR, 3, lhs, one, rhs);
ternary->data.operator= "?:";
node_finalize(node);
return ternary;
}
return node;
}
// Constant folds the given OPERATOR node, if all its children are NUMBER_LITERAL
static node_t* constant_fold_operator(node_t* node)
{
assert(node->type == OPERATOR);
// Check that all operands are NUMBER_LITERALs
for (size_t i = 0; i < node->n_children; i++)
if (node->children[i]->type != NUMBER_LITERAL)
return node;
const char* op = node->data.operator;
// This is where we store the result of the constant fold
int64_t result;
if (node->n_children == 1)
{
int64_t operand = node->children[0]->data.number_literal;
if (strcmp(op, "-") == 0)
result = -operand;
else if (strcmp(op, "!") == 0)
result = !operand;
else
assert(false && "Unknown unary operator");
}
else if (node->n_children == 2)
{
// We do not need to handle "and" or "or" here, as they have been converted to ternary ?:
int64_t lhs = node->children[0]->data.number_literal;
int64_t rhs = node->children[1]->data.number_literal;
if (strcmp(op, "==") == 0)
result = lhs == rhs;
else if (strcmp(op, "!=") == 0)
result = lhs != rhs;
else if (strcmp(op, "<") == 0)
result = lhs < rhs;
else if (strcmp(op, "<=") == 0)
result = lhs <= rhs;
else if (strcmp(op, ">") == 0)
result = lhs > rhs;
else if (strcmp(op, ">=") == 0)
result = lhs >= rhs;
else if (strcmp(op, "+") == 0)
result = lhs + rhs;
else if (strcmp(op, "-") == 0)
result = lhs - rhs;
else if (strcmp(op, "*") == 0)
result = lhs * rhs;
else if (strcmp(op, "/") == 0)
result = lhs / rhs;
else
assert(false && "Unknown binary operator");
}
else if (node->n_children == 3)
{
int64_t condition = node->children[0]->data.number_literal;
int64_t lhs = node->children[1]->data.number_literal;
int64_t rhs = node->children[2]->data.number_literal;
if (strcmp(op, "?:") == 0)
result = condition ? lhs : rhs;
else
assert(false && "Unknown ternary operator");
}
// Free all children, turn the node into a NUMBER_LITERAL
for (size_t i = 0; i < node->n_children; i++)
destroy_subtree(node->children[i]);
node->type = NUMBER_LITERAL;
node->data.number_literal = result;
node->n_children = 0;
return node;
}
// Convert PRINTLN_STATEMENT into PRINT_STATEMENT by appending an extra "\n"
static node_t* simplify_println_statement(node_t* node)
{
assert(node->type == PRINTLN_STATEMENT);
node->type = PRINT_STATEMENT;
// Create a string literal containing "\n"
node_t* newline_literal = node_create(STRING_LITERAL, 0);
newline_literal->data.string_literal = strdup("\"\\n\"");
append_to_list_node(node->children[0], newline_literal);
return node;
}
// Flatten all LOCAL_VARIABLE_DELCARATION nodes in the block, by converting them
// into individual LOCAL_VARIABLE nodes, and splitting all variable initializations
// into separate ASSIGNMENT_STATEMENT nodes.
static node_t* flatten_variable_declarations(node_t* block)
{
assert(block->type == BLOCK);
node_t* old_list = block->children[0];
node_t* new_list = node_create(LIST, 0);
block->children[0] = new_list;
// Move over statements from the old list to the new list
// LOCAL_VARIABLE_DECLARATIONs are split up
for (size_t i = 0; i < old_list->n_children; i++)
{
node_t* node = old_list->children[i];
if (node->type != LOCAL_VARIABLE_DECLARATION)
{
append_to_list_node(new_list, node);
continue;
}
// Copy over each LOCAL_VARIABLE node to the new list
node_t* list = node->children[0];
for (size_t j = 0; j < list->n_children; j++)
{
node_t* local_variable = list->children[j];
append_to_list_node(new_list, local_variable);
// if the local variable has an initial value, move it to an assignment statement
if (local_variable->n_children == 2)
{
// Only keep the identifier child
local_variable->n_children = 1;
node_t* identifier = local_variable->children[0];
node_t* expression = local_variable->children[1];
// Create the assignment statement using the expression child
node_t* dup_identifier = node_create(IDENTIFIER, 0);
dup_identifier->data.identifier = strdup(identifier->data.identifier);
node_t* assignment_statement =
node_create(ASSIGNMENT_STATEMENT, 2, dup_identifier, expression);
append_to_list_node(new_list, assignment_statement);
}
}
// Delete the LOCAL_VARIABLE_DECLARATION node and its LIST
list->n_children = 0;
destroy_subtree(node);
}
// Clean up the node used for the old list, but do not free any children
node_finalize(old_list);
return block;
}
// Recursively performs simplifcation of the syntax tree:
// - replacing "and" and "or" operators with the ternary ?: operator
// - constant folding operators where all operands are NUMBER_DATA
// - replacing all PRINTLN_STATEMENT with PRINT_STATEMENT with an extra "\n"
// - flattening LOCAL_VARIABLE_DECLARATION nodes into LOCAL_VARIABLE and ASSIGNMENT
//
// Returns the root of the new subtree.
// Any node that is detached from the tree by this operation must be freed, to avoid memory leaks.
static node_t* simplify_subtree(node_t* node)
{
if (node == NULL)
return node;
// First simplify all child nodes
for (size_t i = 0; i < node->n_children; i++)
node->children[i] = simplify_subtree(node->children[i]);
if (node->type == OPERATOR)
{
node = convert_operator(node);
return constant_fold_operator(node);
}
if (node->type == PRINTLN_STATEMENT)
return simplify_println_statement(node);
if (node->type == BLOCK)
return flatten_variable_declarations(node);
return node;
}
// Frees the memory owned by the given node, but does not touch its children
static void node_finalize(node_t* discard)
{
if (discard == NULL)
return;
// Only free data if the data field is owned by the node
switch (discard->type)
{
case IDENTIFIER:
free(discard->data.identifier);
break;
case STRING_LITERAL:
free(discard->data.string_literal);
break;
default:
break;
}
free(discard->children);
free(discard);
}
// Recursively frees the memory owned by the given node, and all its children
static void destroy_subtree(node_t* discard)
{
if (discard == NULL)
return;
for (size_t i = 0; i < discard->n_children; i++)
destroy_subtree(discard->children[i]);
node_finalize(discard);
}
// Definition of the global string array NODE_TYPE_NAMES
const char* NODE_TYPE_NAMES[NODE_TYPE_COUNT] = {
#define NODE_TYPE(node_type) #node_type
#include "nodetypes.h"
};

64
ps4/src/tree.h Normal file
View File

@@ -0,0 +1,64 @@
#ifndef TREE_H
#define TREE_H
#include <stdint.h>
#include <stdlib.h>
// Create the node_type_t enum containing all node types defined in nodetypes.h
typedef enum
{
#define NODE_TYPE(node_type) node_type
#include "nodetypes.h"
NODE_TYPE_COUNT
} node_type_t;
// Array containing human-readable names for all node types
extern const char* NODE_TYPE_NAMES[NODE_TYPE_COUNT];
// This is the tree node structure for the abstract syntax tree
typedef struct node
{
node_type_t type;
struct node** children; // An owned list of pointers to child nodes
size_t n_children; // The length of the list of child nodes
// At most one of the data fields can be used at once.
// The node's type decides which field is active, if any
union
{
const char* operator; // pointer to constant string, such as "+". Not owned
char* identifier; // owned heap allocation. The identifier as a string
int64_t number_literal; // the literal integer value
char* string_literal; // owned heap allocation. Includes the surrounding "quotation marks"
size_t string_list_index; // position in global string list
} data;
// A pointer to the symbol this node references. Not owned.
// Only used by IDENTIFIER nodes that reference symbols defined elsewhere.
struct symbol* symbol;
} node_t;
// Global root for parse tree and abstract syntax tree
extern node_t* root;
// The node creation function, used by the parser
node_t* node_create(node_type_t type, size_t n_children, ...);
// Append an element to the given LIST node, returns the list node
node_t* append_to_list_node(node_t* list_node, node_t* element);
// Outputs the entire syntax tree to the terminal
void print_syntax_tree(void);
// Performs constant folding and node replacements
void simplify_syntax_tree(void);
// Cleans up the entire syntax tree
void destroy_syntax_tree(void);
// Special function used when syntax trees are output as graphviz graphs.
// Implemented in graphviz_output.c
void graphviz_node_print(node_t* root);
#endif // TREE_H

75
ps4/src/vslc.c Normal file
View File

@@ -0,0 +1,75 @@
#include "vslc.h"
#include <getopt.h>
static bool print_full_tree = false;
static bool print_simplified_tree = false;
static bool print_symbol_table_contents = false;
static const char* usage = "Compiler for VSL. The input program is read from stdin."
"\n"
"Options:\n"
"\t -h \t Output this text and exit\n"
"\t -t \t Output the abstract syntax tree\n"
"\t -T \t Output the abstract syntax tree after constant folding\n"
"\t -s \t Output the symbol table contents\n";
// Command line option parsing
static void options(int argc, char** argv)
{
if (argc == 1)
{
fprintf(stderr, "%s: expected at last one option. See -h for help\n", argv[0]);
exit(EXIT_FAILURE);
}
while (true)
{
switch (getopt(argc, argv, "htTs"))
{
default: // Unrecognized option
fprintf(stderr, "%s: See -h for help\n", argv[0]);
exit(EXIT_FAILURE);
case 'h':
printf("%s:\n%s", argv[0], usage);
exit(EXIT_SUCCESS);
case 't':
print_full_tree = true;
break;
case 'T':
print_simplified_tree = true;
break;
case 's':
print_symbol_table_contents = true;
break;
case -1:
return; // Done parsing options
}
}
}
// Entry point
int main(int argc, char** argv)
{
options(argc, argv);
yyparse(); // Generated from grammar/bison, constructs syntax tree
yylex_destroy(); // Free buffers used by flex
// Operations in tree.c
if (print_full_tree)
print_syntax_tree();
simplify_syntax_tree();
if (print_simplified_tree)
print_syntax_tree();
// Operations in symbols.c
create_tables();
if (print_symbol_table_contents)
print_tables();
destroy_tables(); // In symbols.c
destroy_syntax_tree(); // In tree.c
}

23
ps4/src/vslc.h Normal file
View File

@@ -0,0 +1,23 @@
#ifndef VSLC_H
#define VSLC_H
#include <assert.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Definition of the tree node type, and functions for handling the parse tree
#include "tree.h"
// Definition of the symbol table, and functions for building it
#include "symbols.h"
// The main driver function of the parser generated by bison
int yyparse();
// A "hidden" cleanup function in flex
int yylex_destroy();
#endif // VSLC_H

View File

@@ -0,0 +1,14 @@
var array[3], other[20]
func sum() {
return array[0] + array[1] + array[2]
}
func main() {
array[0] = 5
array[1] = 1
array[2] = array[1]
other[sum()] = sum()
println("Should be 7:", other[7])
}

View File

@@ -0,0 +1,27 @@
func main(a, b) {
if (1)
println("Always")
if (0)
println("Never")
else
println("This, however!")
if (a > b) {
println(a, ">", b)
}
else if (a < b) {
println(a, "<", b)
}
else {
println(a, "=", b)
}
// Now test dangling else
if (a)
if (b)
println("a & b")
else
println("a, but not b")
}

View File

@@ -0,0 +1,34 @@
func main() {
var a = 1
var b = 4 - 1 - 1
var c = -a * b + 4 * a * 1 + 2
a = a / 2 + 1
if (a * a < b + c)
println("Yes")
if (a == 1 or b == 2)
println("Either!")
if (a == 1 and b == 2)
println("Both!")
if (!a == 0)
println("Yup")
if (a <= b == b > a)
print("Alawys!")
if (a <= b != a >= b)
println("a != b")
if (a == b == (a != b))
println("Never!")
a = a > 4 or b < 2 ? a + 4 : b - 2
c = (a <= b) + --(a >= b)
return c
}

View File

@@ -0,0 +1,12 @@
func main() {
var localVariable
globalVariable = 10
localVariable = 3
println("Global:", globalVariable, " local:", localVariable)
}
// Global variables can be declared anywhere in the file
var globalVariable

View File

@@ -0,0 +1,16 @@
func main() {
print("Should be 7:", identity(first(second(5, 7), first(2, 9))))
}
func identity(argument) {
return argument
}
func first(a, b) {
return a
}
func second(a, b) {
return b
}

View File

@@ -0,0 +1,5 @@
func main() {
print("Hello ")
println("World!")
}

View File

@@ -0,0 +1,30 @@
var global, myArray[10]
func main() {
var a, b = 2
a = 5
var c
global = 3
// A block is itself a statement
{
var d = a + b * c
println("d:", d)
}
if (1) {
var x
x = a*b*c
if (x > 0) {
// Declare a new x, shadowing the outer x
var x
x = a + global
} else {
x = a
myArray[global] = 2
}
println("x:", x)
}
}

View File

@@ -0,0 +1,20 @@
func callMe(i) {
println("i is now", i)
}
func main(start, end) {
var counter = start
while (counter < end) {
callMe(counter)
counter = counter + 1
}
// Go down again using while 1 + break
while (1) {
counter = counter - 1
if (counter < start)
break
callMe(counter)
}
}

View File

@@ -0,0 +1,14 @@
func other() {
return 1
}
func main(a) {
if (a and other())
print("Hei")
if (a or other())
print("Yo")
if (a > 2 or other() - 1 and a + 1 < 10)
print("Nope")
}

View File

@@ -0,0 +1,26 @@
func main() {
print(3 + 5)
print(3 - -6)
print(20 * 4)
print(111 / 11)
print(50 and 10)
print(0 or 20)
print(0 and 1)
print(1 ? 5 : 7)
print(0 ? 20 : 30)
return other()
}
func other() {
var x
x = !5 == !6
x = !(5 != 6)
x = 10 < 10
x = 10 <= 10
x = 10 > 10
x = 10 >= 10
x = 40 + 20 + x * 2
return x
}

View File

@@ -0,0 +1,6 @@
func main() {
print(20, " and ", 40)
println("!")
println(50, " and ", 50)
}

View File

@@ -0,0 +1,10 @@
func main() {
var a, b = 2, c = b
print(b)
var c
c = 10
print(c)
}

View File

@@ -0,0 +1,28 @@
func main() {
var a = 2, b = 6
while (a + b < 10) {
a = a + 1
var tmp = a
a = b
b = tmp
}
if (a < b) {
var tmp
tmp = a
a = b
b = tmp
}
return recursive(a)
}
func recursive(x) {
if (x == 0)
return 1
var prev = recursive(x - 1)
return prev * 2 + 1
}

View File

@@ -0,0 +1,39 @@
var x, y, z
func main(x, y) {
var x = y + 2
if (x > 10) {
var x = 6, y = 2
z = x + y
{
var y = 20 + x
x = y
}
z = y
}
y = y + other(x)
return y
}
func other(x) {
var z = x + 1
{
var x = z + 1
{
var z = x + 1
{
var x = z + 1
{
z = x + 1
}
}
x = z + 1
}
z = x + 1
}
x = z + 1
return x
}

View File

@@ -0,0 +1,10 @@
var a, b
func main()
return a + x + func2()
func func2()
return array[other[5]]
var array[2], other[10]
var x

View File

@@ -0,0 +1,17 @@
func main() {
var a, b = 2
var c
a = 6
if (a > b)
c = 2
else
c = 6
return other(a, b, c)
}
func other(x, y, z) {
return x + y * 2
}

View File

@@ -0,0 +1,10 @@
func main() {
print("Hello ", 2, " World")
{
println("Another one")
}
}
func printNewlines() {
print("\n\n\n")
}

View File

@@ -0,0 +1,18 @@
var glob
func a() {
println("Hei", " ", "and", " ", "hello")
glob = 10
while (1) {
println("Cool")
if (glob == 0) {
println("Done!")
break
}
glob = glob - 1
println("Glob is now: ", glob)
}
return 5
}