Skip to content

Commit

Permalink
Merge pull request #2 from anirudhgray/implement-hashmap
Browse files Browse the repository at this point in the history
Implement hashmap
  • Loading branch information
anirudhgray authored Oct 27, 2024
2 parents f179d4d + 7b39fab commit 2bc2886
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 13 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ print percentage(20, 28);
- [Contracts (Preconditions, Postconditions and Assertions)](#contracts-preconditions-postconditions-and-assertions)
- [Unit Tests](#unit-tests)
- [Arrays](#arrays)
- [Maps](#maps)
- [Conditionals](#conditionals)
- [Loops](#loops)
- [Built-in Functions](#built-in-functions)
Expand All @@ -60,7 +61,7 @@ Tahini currently implements:
- [x] **Conditionals**: If-else statements for decision-making.
- [x] **Functions**: First class citizens of Tahini. Define and call reusable blocks of code, with support for contracts (`precondition`, `postcondition`, and `assertion`).
- [ ] **Classes**: Object-oriented features to group variables and methods (halted in favour of a lean towards a functional paradigm).
- [ ] **Advanced Data Structures**: Support for lists, maps, and other data structures (in progress).
- [x] **Advanced Data Structures**: Basic support for lists, maps, and other data structures.
- [ ] **Error Handling**: Support for user-defined exceptions and error handling (in progress).
- [x] **Stack Traces**: Detailed error messages with line numbers and function names.
- [x] **Unit Tests**: Write test blocks directly in the source file to validate code correctness.
Expand Down Expand Up @@ -317,6 +318,15 @@ fun remove(arr, index) {
}
```

### Maps

Maps are implemented as a HashMap. You can create a map via `{...}` syntax, and access elements using the `[]` operator. Maps can contain any object keys or values.

```
var map = {"key": "value", 1: 2, "fib": fib};
print map["key"]; // value
```

### Conditionals

```
Expand Down
15 changes: 15 additions & 0 deletions tahini/app/src/main/java/com/tahini/lang/Expr.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface Visitor<R> {
R visitVariableExpr(Variable expr);
R visitLogicalExpr(Logical expr);
R visitTahiniListExpr(TahiniList expr);
R visitTahiniMapExpr(TahiniMap expr);
}
static class Assign extends Expr {
Assign(Token name, Expr value) {
Expand Down Expand Up @@ -191,6 +192,20 @@ <R> R accept(Visitor<R> visitor) {

final List<Expr> elements;
}
static class TahiniMap extends Expr {
TahiniMap(List<Expr> keys, List<Expr> values) {
this.keys = keys;
this.values = values;
}

@Override
<R> R accept(Visitor<R> visitor) {
return visitor.visitTahiniMapExpr(this);
}

final List<Expr> keys;
final List<Expr> values;
}

abstract <R> R accept(Visitor<R> visitor);
}
37 changes: 29 additions & 8 deletions tahini/app/src/main/java/com/tahini/lang/Interpreter.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.tahini.lang;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;

class Interpreter implements Expr.Visitor<Object>, Stmt.Visitor<Void> {
Expand Down Expand Up @@ -85,6 +87,17 @@ public Object visitTahiniListExpr(Expr.TahiniList expr) {
return tahiniList;
}

@Override
public Object visitTahiniMapExpr(Expr.TahiniMap expr) {
Map<Object, Object> tahiniMap = new HashMap<>();
for (int i = 0; i < expr.keys.size(); i++) {
Object key = evaluate(expr.keys.get(i));
Object value = evaluate(expr.values.get(i));
tahiniMap.put(key, value);
}
return tahiniMap;
}

@Override
public Object visitLiteralExpr(Expr.Literal expr) {
return expr.value;
Expand Down Expand Up @@ -376,29 +389,37 @@ public Object visitListAccessExpr(Expr.ListAccess expr) {
Object collection = evaluate(expr.list);
Object index = evaluate(expr.index);

if (!(collection instanceof List || collection instanceof String)) {
throw new RuntimeError(expr.paren, "Can only access elements of a list or a string.", new ArrayList<>());
}

if (!(index instanceof Double)) {
throw new RuntimeError(expr.paren, "Index must be a number.", new ArrayList<>());
if (!(collection instanceof List || collection instanceof String || collection instanceof Map)) {
throw new RuntimeError(expr.paren, "Can only access elements of a list, map or a string.", new ArrayList<>());
}

int i = ((Double) index).intValue();

return switch (collection) {
case List<?> list -> {
if (!(index instanceof Double)) {
throw new RuntimeError(expr.paren, "Index must be a number for list access.", new ArrayList<>());
}
int i = ((Double) index).intValue();
if (i < 0 || i >= list.size()) {
throw new RuntimeError(expr.paren, "Index out of bounds.", new ArrayList<>());
}
yield list.get(i);
}
case String str -> {
if (!(index instanceof Double)) {
throw new RuntimeError(expr.paren, "Index must be a number for string access.", new ArrayList<>());
}
int i = ((Double) index).intValue();
if (i < 0 || i >= str.length()) {
throw new RuntimeError(expr.paren, "Index out of bounds.", new ArrayList<>());
}
yield String.valueOf(str.charAt(i));
}
case Map<?, ?> map -> {
if (!map.containsKey(index)) {
throw new RuntimeError(expr.paren, "Key not found in map.", new ArrayList<>());
}
yield map.get(index);
}
default ->
throw new RuntimeError(expr.paren, "Unexpected error.", new ArrayList<>());
};
Expand Down
19 changes: 17 additions & 2 deletions tahini/app/src/main/java/com/tahini/lang/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -518,10 +518,10 @@ private Expr call() {
Expr index = expression();
if (match(TokenType.COLON)) {
Expr end = expression();
consume(TokenType.RIGHT_SQUARE, "Expect ']' after list slice.");
consume(TokenType.RIGHT_SQUARE, "Expect ']' after slice.");
expr = new Expr.ListSlice(expr, paren, index, end);
} else {
consume(TokenType.RIGHT_SQUARE, "Expect ']' after list index.");
consume(TokenType.RIGHT_SQUARE, "Expect ']' after index.");
expr = new Expr.ListAccess(expr, paren, index);
}
} else {
Expand Down Expand Up @@ -565,6 +565,21 @@ private Expr primary() {
return new Expr.TahiniList(elements);
}

// hash map
if (match(TokenType.LEFT_BRACE)) {
List<Expr> keys = new ArrayList<>();
List<Expr> values = new ArrayList<>();
if (!check(TokenType.RIGHT_BRACE)) {
do {
keys.add(expression());
consume(TokenType.COLON, "Expect ':' after key.");
values.add(expression());
} while (match(TokenType.COMMA));
}
consume(TokenType.RIGHT_BRACE, "Expect '}' after map elements.");
return new Expr.TahiniMap(keys, values);
}

// Error production for binary operator without left-hand operand
if (match(TokenType.PLUS, TokenType.MINUS, TokenType.STAR, TokenType.SLASH, TokenType.EQUAL_EQUAL, TokenType.BANG_EQUAL, TokenType.GREATER, TokenType.GREATER_EQUAL, TokenType.LESS, TokenType.LESS_EQUAL)) {
Token operator = previous();
Expand Down
3 changes: 2 additions & 1 deletion tahini/app/src/main/java/com/tahini/tool/GenerateAst.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ public static void main(String[] args) throws IOException {
"Ternary : Expr condition, Expr left, Expr right",
"Variable : Token name",
"Logical : Expr left, Token operator, Expr right",
"TahiniList : List<Expr> elements"
"TahiniList : List<Expr> elements",
"TahiniMap : List<Expr> keys, List<Expr> values"
));

defineAst(outputDir, "Stmt", Arrays.asList(
Expand Down
2 changes: 1 addition & 1 deletion tahini/tests/arrays3.tah
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
var xy = 10;
print xy[2];

// RuntimeError: Can only access elements of a list or a string.
// RuntimeError: Can only access elements of a list, map or a string.
// [line 2]
4 changes: 4 additions & 0 deletions tahini/tests/map.tah
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
var map = { "x": 10, "y": 20 };
print map["x"];

// 10
10 changes: 10 additions & 0 deletions test.tah
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ fun isEven(n) {
return n == 0;
}

var dictwithfunc = { "key1": 1, "key2": 2, "key3": 3, true: false, nil:5, "fib": isEven, isEven: isEven };
print dictwithfunc["key1"];
print dictwithfunc[true];
print dictwithfunc[nil];
print dictwithfunc["fib"](4);
print dictwithfunc["fib"];
print dictwithfunc[isEven];
print dictwithfunc[isEven](5);
print dictwithfunc["isEven"](4);

print len("Hello");
var str = "hello";
print len(str);
Expand Down

0 comments on commit 2bc2886

Please sign in to comment.