# Dolphin -- Phase 5

```{attention}
This is a group assignment. The workload is calibrated for a group of 3.
Please also see the [recommended workflow](assign-8-appendix-workflow) section in the Appendix below.

In case of questions regarding ambiguity of what you should do, ask questions on the forum. If you are in doubt and there is no enough time, use your best judgment and explain your reasoning in your report.
```

## Assignment overview

This assignment extends the language with three language features: records, arrays, and strings.
For examples of programs that use these features, we refer to
the [Exercises for week 44](exercises-week-44), and the example programs
provided with this assignment.

There are 6 tasks and no questions in this assignment. There is one _glory_ task. Do it only if you have the time and feel ambitious.

### What you need to get started

- This assignment is a continuation of the previous assignment. You will need 
  to edit the code from the previous assignment.
- The following features of LLVM that we have not previously used are relevant for this assignment   
   - [LLVM named type definitions](llvm-typed-definitions) and [agggregate types](llvm-aggregate-types).
   -  [LLVM global identifiers](llvm-global-data-definitions) and string literals.
   - LLVM cast operation `ptrtoint`. 
   - LLVM's `getelemntptr` instruction. See [our reference material on GEP](section-gep) and [Exercises for week 45](exercise-week-45-llvm).
- You need to understand the OCamllex lexer generator. Refer to [OCamllex documentation](https://v2.ocaml.org/manual/lexyacc.html#s%3Aocamllex-overview) for details.
- You need to understand the Menhir parser generator. Refer to [Menhir documentation](https://gallium.inria.fr/~fpottier/menhir/) for details.

```{important}
You will need files `deserializer.ml` and `deserializer.mli` for Phase 5 as they have been released in the TA classes.
```

### What you need to hand in
Please hand in a `.zip` file named `group<XY>.zip` (replace `<XY>` by your group number) containing the following

1. A brief report documenting your solution. Acceptable report formats are `.pdf`, `.rtf`, and `.md`. For each task and question, briefly (1 – 4 sentences) describe your implementation or answer. Write concisely.
2. All the source files needed to reproduce your solution. This also includes the `C` code provided. Please explain in your report how the solution could be reproduced, e.g., calling `make` (if you have made a `Makefile`), the command line to call `clang`, etc.
We recommend using the `dune` and `dune-project` released in class.
3. All the tests that you create (see Task 5) should be placed into a directory `assignment-08-tests` as individual `.dlp` files.

Before submitting, check that your zip file is good for submission with the script `presub.sh` available in the docker container.
You can use it with the following command in your terminal: `./presub.sh <phase> <path_to_zip>`.
You must replace `<phase>` by the phase of the assignment (1,2,3,4 or 5), and `<path_to_zip>` by the path to you zip file.
It checks that:
- all required files are present and not empty
- your project compiles with `dune`
- your compiler can compile the simplest Dolphin source code `return 0;`.

```{important}
Make sure to understand all the code you hand in. (The code for the deserializer is an exception here.)
```

## Records in Dolphin

Records in Dolphin are similar to structs in C (or Java classes without methods). They are the only form of user-defined data types. 
Records allow grouping data of different types under a single entity.
Record types are declared using the `record` keyword, which has the following syntax:
```dolphin
record <record-type-name> {
  <field-name-1>:<type-1>;
  <field-name-2>:<type-2>;
  ...
  <field-name-n>:<type-n>;
}
```
For example,  `record Tuple { x: int; y : int; }`. A record type is referred to in the program using its name, for example via `var x:MyRec`.

New records are declared using the syntax 
```dolphin
new <record-type-name> {
  <field-name-1> = <field-init-expression-1>;
  <field-name-2> = <field-init-expression-2>;
  ...
  <field-name-n> = <field-init-expression-n>;
}
```
where
each `<field-init-expressions>` must have the type corresponding to the field it initializes.

Given an expression `e` of record type, its field `f` is accessed using the dot notation: `e.f`.
The keyword `nil` denotes an invalid record of any record type, like `null` in Java; attempting to access one of its fields should be reported as an error at runtime.
Below is an example of a simple program with records:

```dolphin
record Tuple { x: int; y : int ; } 

int main () {
    var a:Tuple = new Tuple { x = 0; y = 1; };
    var b:Tuple = nil;
    return a.x;  /* dot notation to refer to the field `x` of variable `a` */
}
```

The following applies to records and their use in Dolphin programs.

1. All records are declared at the top-level. A Dolphin program is a collection of function and record declarations.

2. All records are mutually recursive.

3. Within a program, all record names must be unique.

4. The following record names are _reserved_ for the standard library. They may not be used in the declarations of the user-defined records: `stream`, `socket`, `socket_address`, `ip_version`, `accepted_connection`, `udp_recvfrom_result`, and `connection_type`.

5. The only way to obtain non-nil values for the reserved records is through standard library. In particular, reserved records cannot be created in the program using the `new` keyword.

6. All fields within the record must be unique.

7. The number of fields may be zero.

8. At record creation, the fields may appear in any order. For example, 
`var a:Tuple = new Tuple { y = 1; x = 0; };` is a valid record creation. 

9. All fields must be initialized. If a field initialization is missing, an error must be reported.

10. The only binary operations allowed on the records are equalty and inequality. Records are compared by reference. The following example program illustrates record equality.
```dolphin
/* valid program; returns 1 */
record Tuple { x: int; y : int ; } 

int main () {
    var a:Tuple = new Tuple { x = 0; y = 1;};
    var b:Tuple = new Tuple { x = 0; y = 1;};
    var c = a;
    if (b == c) {
        return 0;
    }
    if (a == c) {
        return 1;
    }   
    return 2;
}
```

11. If one of the operands of equality (or inequality) is a record, the other operand must be either (a) another record of the same type, or (b) `nil`.

```{note}
This particular design of record comparison is compatible with mainstream languages such as Java and C. This also means that other ways of definging equality,  i.e., structurally, have to be implemented in code, e.g., by writing a function `bool tuple_eq (Tuple t1, Tuple t2)`.
```


## Arrays in Dolphin

Arrays in Dolphin are similar to arrays in languages such as Java or C. They hold a fixed number of values of a single type. The type of the array is denoted using the syntax  `[<element-type>]`, e.g., `[int]` is the type of array of integers. Arrays are initialized using the syntax `new <element-type> [<length-expression>]`, where `<length-expression>` must evaluate to an integer. For example, `var x = new int[1+3]` initializes `x` to be an array of 4 elements. The length of the arrays is, in general, not known at compile time. Arrays are indexed using the bracket notation `<array-expression> [ <index-expression> ]`.

The following applies to arrays:

1. All array accesses -- reading and writing -- are bounds-checked.
2. Similar to records, the only allowed binary operations on 
   arrays are equality and inequality. Similar to records, 
   the equality checks are done by reference.
3. The number of array elements must be non-negative.
4. After array creation, the length of the array cannot change at runtime.
5. Array length is accessed using `length_of (<expression>)`, where `<expression>` must evaluate to an array, and `length_of` is a keyword.
(`length_of` also supports strings; see below.)

## Strings in Dolphin

In Dolphin, strings are a built-in type. They can be created in one of the following ways:

- using string literals in the program source, e.g,. `"Hello"`
- using Dolphin standard library functions, e.g., `string_concat ("Hello", "World")`
  concatenates two strings.

The following applies to strings:

1. The binary operations on strings are equality, inequality, and string comparison. Unlike arrays 
and records, strings are compared by value.

2. String literals may include escape characters: for example '\n' stands for a newline. Dolphin follows [OCaml's lexical convention](https://v2.ocaml.org/manual/lex.html#sss:character-literals) for representing escape sequences (which can be handled using OCaml's [`Scanf.unescaped`](https://v2.ocaml.org/api/Scanf.html#VALunescaped)).

3. String literals may span over several lines.

5. The length of a string can be obtained using `length_of` keyword. 

The following example program illustrates some string operations:
```dolphin
int main() {
    var x = "hello
world";
    var y = "hello\nworld";
    if (x == y) {
        return 0;
    }
    return length_of(x);
}
```

## The Abstract Syntax Tree (AST) of Dolphin (phase 5)

```ocaml
(* -- Use this in your solution without modifications *)

module Loc = Location

type recordname = RecordName of {name : string; loc : Loc.location}

type fieldname = FieldName of {name : string; loc : Loc.location}

type ident = Ident of {name : string; loc : Loc.location}

type typ =
| Int of {loc : Loc.location}
| Bool of {loc : Loc.location}
| Str of {loc : Loc.location}
| Byte of {loc : Loc.location}
| Array of {typ : typ; loc : Loc.location}
| Record of {recordname : recordname}

type rettyp =
| Void of {loc : Loc.location}
| RetTyp of typ

type binop =
| Plus of {loc : Loc.location}
| Minus of {loc : Loc.location}
| Mul of {loc : Loc.location}
| Div of {loc : Loc.location}
| Rem of {loc : Loc.location}
| Lt of {loc : Loc.location}
| Le of {loc : Loc.location}
| Gt of {loc : Loc.location}
| Ge of {loc : Loc.location}
| Lor of {loc : Loc.location}
| Land of {loc : Loc.location}
| Eq of {loc : Loc.location}
| NEq of {loc : Loc.location}

type unop =
| Neg of {loc : Loc.location}
| Lnot of {loc : Loc.location}

type expr =
| Integer of {int : int64; loc : Loc.location}
| Boolean of {bool : bool; loc : Loc.location}
| String of {string : string; loc : Loc.location}
| BinOp of {left : expr; op : binop; right : expr; loc : Loc.location}
| UnOp of {op : unop; operand : expr; loc : Loc.location}
| LengthOf of {expr : expr; loc : Loc.location}
| Lval of lval
| Assignment of {lvl : lval; rhs : expr; loc : Loc.location}
| Rcrd of {rcrdtp : recordname; fields : (fieldname * expr) list; loc : Loc.location}
| Arr of {tp : typ; len : expr; loc : Loc.location}
| Nil of {loc : Loc.location}
| CommaExpr of {left : expr; right : expr; loc : Loc.location}
| Call of {fname : ident; args : expr list; loc : Loc.location}
and lval =
| Var of ident
| Idx of {arr : expr; index : expr; loc : Loc.location}
| Fld of {rcrd : expr; field : fieldname; loc : Loc.location}

type single_declaration = Declaration of {name : ident; tp : typ option; body : expr; loc : Loc.location}

type declaration_block =
| DeclBlock of {declarations : single_declaration list; loc : Loc.location}

type for_init =
| FIExpr of expr
| FIDecl of declaration_block

type statement =
| VarDeclStm of declaration_block
| ExprStm of {expr : expr option; loc : Loc.location}
| IfThenElseStm of {cond : expr; thbr : statement; elbro : statement option; loc : Loc.location}
| WhileStm of {cond : expr; body : statement; loc : Loc.location}
| ForStm of {init : for_init option; cond : expr option; update : expr option; body : statement; loc : Loc.location}
| BreakStm of {loc : Loc.location}
| ContinueStm of {loc : Loc.location}
| CompoundStm of {stms : statement list; loc : Loc.location}
| ReturnStm of {ret : expr option; loc : Loc.location}

type fundecl = {ret : rettyp; funname : ident; params : (ident * typ) list; body : statement list; loc : Loc.location}

type recdecl = {recname : recordname; fields : (fieldname * typ) list; loc : Loc.location}

type program_fragment = FunDecl of fundecl | RecDecl of recdecl

type program = program_fragment list
```

```{admonition} Action item
The AST declarations above should replace the contents of the module called `Ast`.
Do **not** change the code above.
```

<!-- As you work on this task, take the following aspects into account. -->


<!-- ### Type representation  -->

<!-- We suggest the following OCaml data structures for representing bytes, strings, arrays, -->
<!-- and records. -->

<!-- ```ocaml -->
<!-- (* suggested code snippet to incorporate into your AST *) -->
<!-- type recordname = RecordName of {name : string; loc : Loc.location} -->
<!-- type fieldname = FieldName of {name : string; loc : Loc.location} -->
<!-- type ident = Ident of {name : string; loc : Loc.location} -->

<!-- type typ = -->
<!-- | ...  (* placeholder for previous constructors *) -->
<!-- | Byte of {loc : Loc.location} -->
<!-- | Str of {loc : Loc.location} -->
<!-- | Array of {typ : typ; loc : Loc.location} -->
<!-- | Record of {recordname : recordname} -->
<!-- ``` -->


<!-- ### Program structure  -->
<!-- The structure of the new AST should follow the idea that a -->
<!-- program is a list of functions or record declarations.  You should create -->
<!-- a way of representing record declarations in the AST. -->


<!-- ### Expressions  -->

<!-- To accommodate the new features, we suggest to extend your `expr` type by -->
<!-- adding constructors for the following: -->
 
<!-- - record creation -->
<!-- - array creation -->
<!-- - `nil` expression -->
<!-- - string values -->
<!-- - the `length_of` keyword -->

<!-- ### Extending Lvals -->

<!-- In programming languages, _lvals_, correspond to -->
<!-- the program entities that may appear to the _left_ of the assignment statement, hence the use of the letter _l_ in the name. In previous phases, lvals were only identifiers. With the addition of records and arrays, the space of -->
<!-- lvals is now richer. It includes indexing into arrays, or accessing a record field, as well as their combination, e.g., `f().x[1+g()].y[2].z`. -->

<!-- With regards to extending the AST to support lvals, we suggest the following.  -->

<!-- ```ocaml -->
<!-- type expr =  -->
<!-- ... -->
<!-- and lval = -->
<!-- | Var of ident -->
<!-- | Idx of { arr   : expr       -->
<!--          ; index : expr -->
<!-- 	 ; loc   : Loc.location -->
<!-- 	 } -->
<!-- | Fld of { rcrd  : expr -->
<!--          ; field : fieldname (* see declaration of fieldname earlier *) -->
<!-- 	 ; loc   : Loc.location -->
<!-- 	 }  -->
<!-- ``` -->


## Lexer 

<!----- TASK ---->
{{ task | replace ("%%NUMBER%%", "1") 
        | replace ("%%NAME%%",   "Extend the lexer to support the new language features")}}

As you work on this task, recognize what new tokens need to be added to the language. 
For full support of strings, you may need to add a new lexer rule, in order to properly treat escape characters and strings spanning multiple lines. Note that because Dolphin follows OCaml's specification of escape characters, we can use the OCaml standard library function [`Scanf.unescape`](https://v2.ocaml.org/api/Scanf.html#VALunescaped) to recognize escape characters.

## Parser
<!----- TASK ---->
{{ task | replace ("%%NUMBER%%", "2") 
        | replace ("%%NAME%%",   "Extend the parser to support the new language features")}}

To score full points on this task, your parser must not have any shift/reduce or reduce/reduce conflicts. As you work on this task, pay attention to proper parsing of lvals in your parser. Consult the examples and the specification earlier in this section regarding the correct syntax, i.e., the use of semicolons when delimiting fields in records. Write your own tests based on the specification.

## Semantic analysis
<!----- TASK ---->
{{ task | replace ("%%NUMBER%%", "3") 
        | replace ("%%NAME%%",   "Extend the semantic analysis to support the new language features")}}


### Record declarations
In the implementation of semantic analysis, pay special attention to the mutual recursion of 
records. Similarly to the mutual recursion of functions, use two passes over the record declarations.

1. Identify all record names, and collect them into a data-structure (a list or a set).
2. Check each record individually, ensuring that the user-defined fields correspond to valid types.

### Checking functions

Once the record declarations are checked, the information about all the records can be collected 
into an environment where their names map to their types. This environment should also include the reserved records. Use this resulting environment when type checking function signatures and bodies.


## Code generation

<!----- TASK ---->
{{ task | replace ("%%NUMBER%%", "4") 
        | replace ("%%NAME%%",   "Extend the code generation to support the new language features")}}

You should have the functions `compile_prog_from_ast` and `compile_prog_from_filename` from the
previous assignment. If implemented properly before, these functions should not require any changes for this assignment.

You should be able to execute your compiler from the CLI, with the command `dolphin compile --phase 5 <path_to_dolphin_program.dlp>`.
You can still test the phases of your compiler after the parser, from a serialized AST, with the command `dolphin compile --rescue --phase 5 <path_to_serialised_ast.json>`.

We start off by going over the runtime and standard library integration.


### Runtime and standard library integration

Before you proceed with code generation, it is necessary to port your project to 
the new runtime. The runtime and the standard library are split across several modules, because there is a qualitative distinction between the runtime and the standard library (unlike in the earlier phases), which we explain below. We have the following files.

1. `runtime.c` contains the extended core runtime functionality, for operations such as record and array allocation, string equality, reporting null pointer access error, etc. What makes these functions part of the runtime (as opposed to the standard library) is that none of these C functions are exposed to the programmer. It is the compiler's code generator that embeds calls to them, based on the (typed) AST.
2. `stdlib.c` contains Dolphin standard library that includes functions that are user-visible. These include a number of "everyday" functionality, such as functions for string concatenation, printing a string, etc. This is a relatively large module.
3. `runtime.h` is a header file that is included from `stdlib.c`.

These files are to be downloaded from Brightspace.


#### Representation of reserved records

We map reserved records, e.g., `stream` to empty LLVM records. This is sufficient because when passing these arguments back and forth to the runtime, we will 
only use pointers to these records.
That is, the LLVM type is just `ptr`.

#### Bytes

In addition, some standard library functions rely on a type `byte`, which should be translated to an LLVM `i8`.

#### Declaring external functions and types

Because your generated LLVM file uses the external functionality provided by
the runtime and standard library, it needs to include `@declare` instructions
to let LLVM know of the function signatures and that they are implemented elsewhere. 

(stdlib-llvm-decls-must-include)=
```llvm
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; The following declarations should be included in the generated LLVM file ;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

; LLVM struct corresponding to the reserved stream type
%dolphin_record_stream = type {  }

; LLVM struct corresponding to array and string representation
%array_type = type {i64, [0 x i8] }

@dolphin_rc_empty_string = external global %array_type

; signatures for the runtime functions 
; -- not user visible --
declare i64 @compare_strings(ptr, ptr)
declare ptr @allocate_record(i32)
declare ptr @raw_allocate_on_heap(i32)
declare ptr @allocate_array(i32, i64, ptr)
declare void @report_error_array_index_out_of_bounds()
declare void @report_error_nil_access()
declare void @report_error_division_by_zero()

; signatures for the core standard library 
; -- these are user visible --
declare ptr @bytes_array_to_string(ptr)
declare ptr @string_to_bytes_array(ptr)
declare i64 @byte_to_int_unsigned(i8)
declare i64 @byte_to_int_signed(i8)
declare i8 @int_to_byte_unsigned(i64)
declare i8 @int_to_byte_signed(i64)
declare i64 @ascii_ord(ptr)
declare ptr @ascii_chr(i64)
declare ptr @string_concat(ptr, ptr)
declare ptr @substring(ptr, i64, i64)
declare ptr @int_to_string(i64)
declare i64 @string_to_int(ptr)
declare i64 @input_byte(ptr)
declare i1 @output_byte(i8, ptr)
declare ptr @input_bytes_array(i64, ptr)
declare void @output_bytes_array(ptr, ptr)
declare void @output_string(ptr, ptr)
declare i1 @seek_in_file(i64, i1, ptr)
declare i64 @pos_in_file(ptr)
declare i1 @close_file(ptr)
declare i1 @flush_file(ptr)
declare i1 @error_in_file(ptr)
declare i1 @end_of_file(ptr)
declare i64 @get_eof()
declare ptr @open_file(ptr, ptr)
declare ptr @get_stdin()
declare ptr @get_stderr()
declare ptr @get_stdout()
declare ptr @get_cmd_args()
declare void @exit(i64)
```


### Records

#### Record representation

We represent records using LLVM structs. The translation is quite straightforward and is one-to-one. A Dolphin record with `n` fields is translated to an LLVM struct with `n` fields. The types of the LLVM  fields should correspond to the Dolphin types. For example, the Dolphin declaration of two types. Values of Dolphin records are always considered as points. This can be seen in the translation of the following two record types:

```dolphin
record T1 { x: int; t2 : T2;}
record T2 { y: int; t1 : T1;}
```

is translated to LLVM as follows

```llvm
%dlp_rec_T2 = type { i64, ptr }
%dlp_rec_T1 = type { i64, ptr }
```

Note that the naming `%dlp_rec_T2` here is compiler-chosen; you may
chose a different naming convention. What is important is that the code generation phase
of the compiler is aware of the mapping between the source and the target types, and 
that of course the generated LLVM code is valid. The reference implementation uses the prefix `dolphin_record_` for this purpose.


#### Nil representation

Nil record values can be represented as `null` in LLVM.

#### Record initialization

Allocation of a record is done through the runtime function `allocate_record`.
This function takes an argument corresponding to the size of the record. Because the 
size depends on LLVM, we need to implement an architecture-independent way of
 obtaining the size information. This is accomplished using the GEP
size hack{cite}`llvm_sizeof_offsetof` as illustrated below.
To actually save the payload of the record, we further need to use a combination of GEP
and store instructions.

For example, consider the following record creation
```dolphin
var t2 = new T2{y = 10; t1 = t1; }  /* for some previously computed value `t1` */
```
The LLVM code for it can look as follows (we abbreviate `getelementptr` as `<GEP>` for brevity in the listing).

```llvm
; Suppose %var_t1 and %var_t2 are the identifiers 
; that we alloca-ed for the source level t1, t2

; GEP size hack 
%size_ptr = getelementptr %dlp_rec_T2, ptr null, i32 1
; Cast ptr to integer
%size = ptrtoint ptr %size_ptr to i32
; Call into the runtime for allocation
%t2_ptr = call ptr @allocate_record (i32 %size)

; Access field y of the struct
%ptr_field_y_of_var_t2 = getelementptr %dlp_rec_T2, ptr %t2_ptr, i32 0, i32 0
; Save 10 in the field y
store i64 10, ptr %ptr_field_y_of_var_t2

; Read from %var_t1
%ptr_t1 = load ptr, ptr %var_t1
; Access field t1 of the struct
%ptr_field_t1_of_var_t2 = getelementptr %dlp_rec_T2, ptr %t2_ptr, i32 0, i32 1
; Save in the field t1
store ptr %ptr_t1, ptr %ptr_field_t1_of_var_t2

; Save in %var_t2
store ptr %t2_ptr, ptr %var_t2
```

#### Record access

To access the record, we need to use the GEP instruction similarly to how it is used in the initialization above.

### Array translation

#### Array representation 

An array is represented as a contiguous block of memory, with the metadata information about the 
length stored in memory before the content.
```
┌─────────┬─────────────────────────────────────────────────┐
│ Length  │                  Array contents                 │
└─────────┴─────────────────────────────────────────────────┘
▲         ▲                                                  
│         │                                                  
│         The start of the array's contents
│         
│ 
The length of the array
```

We will use C99's [Flexible array member](https://en.wikipedia.org/wiki/Flexible_array_member) feature.
That is, we will use the following C struct:

```C
struct array { int64_t len; char contents[]; };
```
Note how the size of the array is not given.
This struct type should be translated to LLVM as follows:

```LLVM
%array_type = type {i64, [0 x i8] }
```

Arrays are represented as pointers to these structs.
That is, on the LLVM side we use `ptr` and on the C side, we use `struct array *`, see e.g., the type of `allocate_array` function in `runtime.c` and in the [LLVM code provided above](stdlib-llvm-decls-must-include).

(codegen-array-initialization)=
#### Array allocation and initialization 
Array allocation and initialization takes place via the function `allocate_array` in the runtime. Note that the last
argument to that function needs to include _a pointer_ to the default initialization value based on the type of the array's elements as follows:

|array element type|default value|
|------------------|-------------|
| `int`            | `0`         |
| `bool`           | `false`     |
| `string`         | `""`        |
| `byte`           | `0`         |
| `[T]` (arrays)   | `nil`       |
| record types     | `nil`       |

#### Array access 

To access the `i`-th element of the array (counting from zero), after using the GEP instruction to obtain a pointer to the contents of the array (the second field in `%array_type`), we can use GEP instruction again with `i` as the first index.

### String translation

#### Runtime representation
The runtime representation of strings is exactly the same as arrays.

#### String literals in LLVM code.

String literals are represented in the source as global identifiers.

A string literal is represented as an LLVM global of LLVM `array_type`. That is, as a struct type with two fields, the first `i64` field stores the length, while the second field of type `[n x i8]`, an LLVM array, stores the contents of the string where `n` is the length of the string literal. Recall that LLVM arrays have static length (and we **only** used here to represent string literals).
Consider the following source program and the associated translation.

```dolphin
int main () {
    var x = "Hello, world!\n"; 
    output_string (x, get_stdout());
    return 0;
}
```

The corresponding LLVM code can look as follows 
```llvm
;...

%dolphin_record_stream = type {  }
%array_type = type {i64, [0 x i8] }

;...

@string_literal$1 = global { i64, [14 x i8] } {i64 14, [14 x i8] c"Hello, world!\0A"}

;...

declare void @output_string(ptr, ptr)
declare ptr @get_stdout()

;...

define i64 @dolphin_fun_main () {
 %x$0 = alloca ptr
 store ptr @string_literal$1, ptr %x$0
 %load_local_var$2 = load ptr, ptr %x$0
 %call$3 = call ptr @get_stdout ()
 call void @output_string (ptr %load_local_var$2, ptr %call$3)
 ret i64 0
after_return$4:
 unreachable
}
```

Note how the global `string_literal$1` is declared of type `{ i64, [14 x i8] }` but is treated in the code to have the type `ptr` — recall that global declarations produce pointers; see [Global data definitions](llvm-global-data-definitions).

### Comparing strings
String comparison is to be translated to calls to the runtime function `compare_strings`. This
function compares two strings lexicographically. It returns `0` if the strings are identical,
`-1` if the first string is less than the second one, and `1` if the first string is greater than the second one.

## Consolidation and testing
<!----- TASK ---->
{{ task | replace ("%%NUMBER%%", "5") 
        | replace ("%%NAME%%",   "Put all the phases together and test your functionality")}}

- Add at least 10 new tests that check for the negative behavior in the semantic analysis and the frontend that you have implemented.
- Add at least 10 new tests that check for the positive behavior of the frontend.
- Run your compiler on the provided example programs and check their behavior.

Describe why your tests are useful.

### Glory task
<!----- TASK ---->
{{ task | replace ("%%NUMBER%%", "6 (glory)") 
        | replace ("%%NAME%%",   "Add support for the full standard library")}}

Full standard library includes networking API. This will allow you to run the HTTP server example (provided among the examples on Brightspace).
See [full standard library signature](llvm-full-std-lib-sig) in the Appendix.

## Appendix

### Example programs

We provide a handful of example programs together with their expected output. Download them from brightspace.

(assign-8-appendix-workflow)=
### Recommended workflow

As a general rule of thumb, work your way very slowly through the assignment, especially because
as you add code generation, the size of the generated LLVM programs will grow substantially, and 
it is crucial that you understand it. Read and test the LLVM code you generate as you incrementally 
add new aspects of code generation. For example, test record creation immediately after you implement
it, even before you complete other aspects of records, e.g., record field lookups.

At a high level, we suggest the following order for the assignment:

1. Implement the most rudimentary support for strings in the frontend. In particular, for lexing, ignore escape characters and newlines.
3. Add runtime integration and code generation for strings. At this point, you should be able to compile and run something as simple as  
   ```dolphin
   int main () {
     output_string ("Hello World!", get_stdout());
     return 0;
   }
   ```  
4. Add support for records through all the compiler phases.
5. Add support for arrays through all the compiler phases. Get the core functionality of arrays (creation, lookup, update) working first, and add bounds-checking afterwards.
6. Add support for lexing of complex strings, including escape characters and newlines.
7. Consolidate. 

(llvm-full-std-lib-sig)=
### Full standard library signature

```llvm
%dolphin_record_udp_recvfrom_result = type { ptr, ptr }
%dolphin_record_accepted_connection = type { ptr, ptr }
%dolphin_record_socket = type {  }
%dolphin_record_socket_address = type {  }
%dolphin_record_ip_address = type {  }
ptr = type {  }
%dolphin_record_connection_type = type {  }
%dolphin_record_stream = type {  }
%array_type = type {i64, [0 x i8] }

@dolphin_rc_empty_string = external global %array_type

declare i64 @compare_strings(ptr, ptr)
declare ptr @allocate_record(i32)
declare ptr @raw_allocate_on_heap(i32)
declare ptr @allocate_array(i32, i64, ptr)
declare void @report_error_array_index_out_of_bounds()
declare void @report_error_nil_access()
declare void @report_error_division_by_zero()
declare ptr @socket_recvfrom_udp(ptr)
declare i64 @socket_sendto_udp(ptr, ptr, ptr)
declare i1 @socket_close(ptr)
declare i1 @socket_activate_udp(ptr)
declare i1 @socket_connect(ptr, ptr)
declare ptr @socket_accept(ptr)
declare i1 @socket_listen(ptr, i64)
declare i1 @socket_bind(ptr, ptr)
declare i64 @get_port_of_socket_address(ptr)
declare ptr @get_ip_address_of_socket_address(ptr)
declare ptr @create_socket_address(ptr, i64)
declare ptr @ip_address_to_string(ptr)
declare ptr @string_to_ip_address(ptr, ptr)
declare ptr @socket_get_output_stream(ptr)
declare ptr @socket_get_input_stream(ptr)
declare ptr @create_socket(ptr, ptr)
declare ptr @get_ipv6_address_any()
declare ptr @get_ipv4_address_any()
declare ptr @get_ipv6()
declare ptr @get_ipv4()
declare ptr @get_tcp_connection_type()
declare ptr @get_udp_connection_type()
declare ptr @bytes_array_to_string(ptr)
declare ptr @string_to_bytes_array(ptr)
declare i64 @byte_to_int_unsigned(i8)
declare i64 @byte_to_int_signed(i8)
declare i8 @int_to_byte_unsigned(i64)
declare i8 @int_to_byte_signed(i64)
declare i64 @ascii_ord(ptr)
declare ptr @ascii_chr(i64)
declare ptr @string_concat(ptr, ptr)
declare ptr @substring(ptr, i64, i64)
declare ptr @int_to_string(i64)
declare i64 @string_to_int(ptr)
declare i64 @input_byte(ptr)
declare i1 @output_byte(i8, ptr)
declare ptr @input_bytes_array(i64, ptr)
declare void @output_bytes_array(ptr, ptr)
declare void @output_string(ptr, ptr)
declare i1 @seek_in_file(i64, i1, ptr)
declare i64 @pos_in_file(ptr)
declare i1 @close_file(ptr)
declare i1 @flush_file(ptr)
declare i1 @error_in_file(ptr)
declare i1 @end_of_file(ptr)
declare i64 @get_eof()
declare ptr @open_file(ptr, ptr)
declare ptr @get_stdin()
declare ptr @get_stderr()
declare ptr @get_stdout()
declare ptr @get_cmd_args()
declare void @exit(i64)
```
