Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Explain how to get started with lab2 in java #13

Merged
merged 2 commits into from
Nov 26, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 98 additions & 3 deletions notes/java.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,105 @@ interface TypedExpr {

The second record `Var` shows a handy shortcut to overriding the method `type`: if the record has an attribute of the right Type and name ("`type`" in this case), it will automotically become the implementation.

You can work with a variable of type `TypedExpr` like in the example above, by using `switch` expressions. If you want to avoid the `default` case, you can use a `sealed interface` instead. To make that work, you have to declare all implementations using `permits` like this:
You can work with a variable of type `TypedExpr` like in the example above, by using `switch` expressions. If you want to avoid the `default` case, you can use a `sealed interface` instead. To make that work, you have to declare all implementations inside the interface (or use `permits`):

```java
sealed interface TypedExpr permits And, Var {
...
sealed interface TypedExpr {
record And(TypedExpr e1, TypedExpr e2) implements TypedExpr {
Type type() {
return new Type.Bool;
}

record Var(String name, Type type) implements TypedExpr { }
}
```

# How to start

This is just one way to start and the ideas given are not complete - be prepared to change everything along the way to a solution that passes all tests. To get started, here is a possible first try at an implementation of the `typecheck` function in the `TypeChecker`:

```java
public AnnotatedProgram typecheck(Program p) {
var funDefs = ((PDefs) p).listdef_.stream()
.map(def -> (DFun) def)
.toList();
signatures = new HashMap<>();
extractSignatures(funDefs);
return new AnnotatedProgram(checkFunDefs(funDefs));
}
```

The first statement extracts a list of function definitions from (the parse-tree) `p` and stores it in `funDefs`. The `var` means that the type of `funDefs` is automatically infered. Then the signatures, which are stored in a field in the `TypeChecker` class, are initialized. To make the last line of this functions work, the type `AnnotatedProgram)` was modified to an inner `record` of the `TypeChecker` in the following way (the functions `extractSignatures` and `checkFunDefs` will be explained below):

```java
record AnnotatedProgram(Map<String, TypedFunDef> defs) { }
```

The type `TypedFunDef` is also defined as an inner `record` of `TypeChecker`:

```java
record TypedFunDef(CType returns, List<TypedArg> args, List<Statement> stms) { }
record TypedArg(String name, CType type) { }
```

... and `Statement` and `CType` are defined in separate files:

```java
sealed interface Statement {
record Decl(String varName, CType type) implements Statement {}
// TODO: add more records for all the statements we need in the typed syntax
}
```

```java
enum CType {
Int, Double, Bool, Void
}
```

A `Type` from the parser syntax can then be translated to a `CType` like this (where this function definition could for example be put into `TypeChecker`):

```java
CType typeFrom(Type type) {
return switch(type) {
case Type_int ignored -> CType.Int;
// TODO: other cases
default -> throw new IllegalStateException("Unexpected value: " + type);
};
}
```

Now, we'll get back to the functions we left unexplained above, `extractSignatures` and `checkFunDefs`. As the name suggests, `extractSignatures` should pass through all function definitions and extract their signatures, which we will need to check the actual function defintions. This is a possible implementaion:

```java
void extractSignatures(List<DFun> funDefs) {
for(var def : funDefs) {
List<CType> argTypes = def.listarg_.stream()
.map(arg -> typeFrom(((ADecl) arg).type_))
.toList();
Signature signature = new Signature(typeFrom(def.type_), argTypes);
if(signatures.put(def.id_, signature) != null) {
throw new TypeException("Signature already defined for " + def.id_);
};
}
}
```

One thing that could be added here (or somewhere else) is, that there should be exactly one function called "main" which returns `int` and has no arguments. We will only give a sketch of an implementation of `checkFunDefs`. One thing to take care of is dealing with contexts in the right way, which needs some data structure like a stack or list. This is not explained in detail here:

```java
HashMap<String, TypedFunDef> checkFunDefs(List<DFun> funDefs) {
var checkedFunDefs = new HashMap<String, TypedFunDef>();
for(var funDef : funDefs) {
// TODO: produce 'args' which should be a list of argument names with their types
// TODO: produce 'contexts' which should be some datastructure storing types of declared variables
TypedFunDef checkedFunDef = new TypedFunDef(typeFrom(funDef.type_),
args,
checkStms(funDef.liststm_, contexts));
if(checkedFunDefs.put(funDef.id_, checkedFunDef) != null) {
throw new TypeException("Function " + funDef.id_ + " already defined!");
}
}
return checkedFunDefs;

```