From 2f28a8de055029c4501a8cfb796e7f4a1af8b719 Mon Sep 17 00:00:00 2001 From: Bobby Date: Thu, 4 Apr 2024 14:08:33 +0000 Subject: added string concatenation --- README.md | 85 +++++++++++++++++++++++---------------------- evaluator/evaluator.go | 13 +++++++ evaluator/evaluator_test.go | 18 ++++++++++ 3 files changed, 74 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 7ce084d..e6527cb 100644 --- a/README.md +++ b/README.md @@ -3,32 +3,32 @@ Interpreted Toy Programming Language written in Go --> # Mana -Mana is a toy programming language written in Go. It is a dynamically typed, interpreted language with a C-like syntax. +Mana is a toy programming language written in Go. It is a dynamically typed, interpreted language with a C-like syntax. -> *Note*: The language is still in development and is not yet usable. The documentation below is a work in progress and is subject to change. +> _Note_: The language is still in development and is not yet usable. The documentation below is a work in progress and is subject to change. ## Development Roadmap -| Implementation | Status | Specification | Example | Tests | -| --- | --- | --- | --- | --- | -| `LetStatement` | ✔️ | Let Statements are used to declare variables | `let x = 5;` | ✔️ | -| `ReturnStatement` | ✔️ | Return Statements are used to return values from functions | `return 5;` | ✔️ | -| `ExpressionStatement` | ✔️ | Expression Statements are used to evaluate expressions | `5 + 5;` | ✔️ | -| `IdentifierExpression` | ✔️ | Identifier Expressions are used to reference variables | `x` | ✔️ | -| `IntegerLiteralExpression` | ✔️ | Integer Literal Expressions are used to represent integer values | `5` | ✔️ | -| `PrefixExpression` | ✔️ | Prefix Expressions are used to represent prefix operators | `!true` | ✔️ | -| `InfixExpression` | ✔️ | Infix Expressions are used to represent infix operators | `5 + 5` | ✔️ | -| `BooleanLiteralExpression` | ✔️ | Boolean Literal Expressions are used to represent boolean values | `true` | ✔️ | -| `IfExpression` | ✔️ | If Expressions are used to represent conditional statements | `if (true) { return 5; }` | ✔️ | -| `BlockStatement` | ✔️ | Block Statements are used to represent blocks of code | `{ let x = 5; return x; }` | ✔️ | -| `FunctionLiteralExpression` | ✔️ | Function Literal Expressions are used to represent function definitions | `fn(x) { return x; }` | ✔️ | -| `CallExpression` | ✔️ | Call Expressions are used to call functions | `add(5, 5)` | ✔️ | -| `StringLiteralExpression` | NYI | String Literal Expressions are used to represent string values | `"Hello, World!"` | NYI | -| `ArrayLiteralExpression` | NYI | Array Literal Expressions are used to represent array values | `[1, 2, 3]` | NYI | -| `IndexExpression` | NYI | Index Expressions are used to index into arrays | `myArray[0]` | NYI | -| `HashLiteralExpression` | NYI | Hash Literal Expressions are used to represent hash values | `{"key": "value"}` | NYI | - -\**NYI = Not Yet Implemented* +| Implementation | Status | Specification | Example | Tests | +| --------------------------- | ------ | ----------------------------------------------------------------------- | -------------------------- | ----- | +| `LetStatement` | ✔️ | Let Statements are used to declare variables | `let x = 5;` | ✔️ | +| `ReturnStatement` | ✔️ | Return Statements are used to return values from functions | `return 5;` | ✔️ | +| `ExpressionStatement` | ✔️ | Expression Statements are used to evaluate expressions | `5 + 5;` | ✔️ | +| `IdentifierExpression` | ✔️ | Identifier Expressions are used to reference variables | `x` | ✔️ | +| `IntegerLiteralExpression` | ✔️ | Integer Literal Expressions are used to represent integer values | `5` | ✔️ | +| `PrefixExpression` | ✔️ | Prefix Expressions are used to represent prefix operators | `!true` | ✔️ | +| `InfixExpression` | ✔️ | Infix Expressions are used to represent infix operators | `5 + 5` | ✔️ | +| `BooleanLiteralExpression` | ✔️ | Boolean Literal Expressions are used to represent boolean values | `true` | ✔️ | +| `IfExpression` | ✔️ | If Expressions are used to represent conditional statements | `if (true) { return 5; }` | ✔️ | +| `BlockStatement` | ✔️ | Block Statements are used to represent blocks of code | `{ let x = 5; return x; }` | ✔️ | +| `FunctionLiteralExpression` | ✔️ | Function Literal Expressions are used to represent function definitions | `fn(x) { return x; }` | ✔️ | +| `CallExpression` | ✔️ | Call Expressions are used to call functions | `add(5, 5)` | ✔️ | +| `StringLiteralExpression` | ✔️ | String Literal Expressions are used to represent string values | `"Hello, World!"` | NYI | +| `ArrayLiteralExpression` | NYI | Array Literal Expressions are used to represent array values | `[1, 2, 3]` | NYI | +| `IndexExpression` | NYI | Index Expressions are used to index into arrays | `myArray[0]` | NYI | +| `HashLiteralExpression` | NYI | Hash Literal Expressions are used to represent hash values | `{"key": "value"}` | NYI | + +\*_NYI = Not Yet Implemented_ ## REPL @@ -72,10 +72,10 @@ fn subtract(x, y) { } // this will either add x and y if x is less than y, or return the difference between x and y -let result = if (x < y) { - add(x, y) -} else { - subtract(x, y) +let result = if (x < y) { + add(x, y) +} else { + subtract(x, y) }; // result = 15 ``` @@ -83,30 +83,30 @@ let result = if (x < y) { Mana is a dynamically typed language. This means that the type of a variable is determined at runtime. The following are the types that Mana supports: -| Type | Description | Example | -| --- | --- | --- | -| `Integer` | A 64-bit signed integer | `5` | -| `Boolean` | A boolean value | `true` | +| Type | Description | Example | +| --------- | ----------------------- | ------- | +| `Integer` | A 64-bit signed integer | `5` | +| `Boolean` | A boolean value | `true` | ## Operators Mana supports the following operators: -| Operator | Description | Example | -| --- | --- | --- | -| `+` | Addition | `5 + 5` | -| `-` | Subtraction | `5 - 5` | -| `*` | Multiplication | `5 * 5` | -| `/` | Division | `5 / 5` | -| `!` | Logical NOT | `!true` | -| `<` | Less Than | `5 < 5` | -| `>` | Greater Than | `5 > 5` | -| `==` | Equal To | `5 == 5` | -| `!=` | Not Equal To | `5 != 5` | +| Operator | Description | Example | +| -------- | -------------- | -------- | +| `+` | Addition | `5 + 5` | +| `-` | Subtraction | `5 - 5` | +| `*` | Multiplication | `5 * 5` | +| `/` | Division | `5 / 5` | +| `!` | Logical NOT | `!true` | +| `<` | Less Than | `5 < 5` | +| `>` | Greater Than | `5 > 5` | +| `==` | Equal To | `5 == 5` | +| `!=` | Not Equal To | `5 != 5` | ## Variables -Variables are declared using the `let` keyword. The variable name is followed by an equals sign and an expression. The expression is evaluated and the result is assigned to the variable. +Variables are declared using the `let` keyword. The variable name is followed by an equals sign and an expression. The expression is evaluated and the result is assigned to the variable. ```rust let x = 5; @@ -173,4 +173,5 @@ go test -v ./... ``` ## License + This project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details. diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 9129a98..0eadd92 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -198,6 +198,8 @@ func evalInfixExpression(operator string, left, right object.Object) object.Obje switch { case left.Type() == object.INTEGER_OBJ && right.Type() == object.INTEGER_OBJ: return evalIntegerInfixExpression(operator, left, right) + case left.Type() == object.STRING_OBJ && right.Type() == object.STRING_OBJ: + return evalStringInfixExpression(operator, left, right) case operator == "==": return nativeBoolToBooleanObject(left == right) case operator == "!=": @@ -260,6 +262,17 @@ func evalIntegerInfixExpression(operator string, left, right object.Object) obje } } +func evalStringInfixExpression(operator string, left, right object.Object) object.Object { + if operator != "+" { + return newError("unknown operator: %s %s %s", left.Type(), operator, right.Type()) + } + + leftVal := left.(*object.String).Value + rightVal := right.(*object.String).Value + + return &object.String{Value: leftVal + rightVal} +} + func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Object { condition := Eval(ie.Condition, env) if isError(condition) { diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index cf9e320..9738d9e 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -181,6 +181,10 @@ func TestErrorhandling(t *testing.T) { "foobar", "identifier not found: foobar", }, + { + `"Hello" - "World"`, + "unknown operator: STRING - STRING", + }, } for _, tt := range tests { @@ -327,3 +331,17 @@ func TestStringLiteral(t *testing.T) { t.Errorf("String has wrong value. got=%q", str.Value) } } + +func TestStringConcatenation(t *testing.T) { + input := `"Hello" + " " + "World!"` + + evaluated := testEval(input) + str, ok := evaluated.(*object.String) + if !ok { + t.Fatalf("object is not String. got=%T (%+v)", evaluated, evaluated) + } + + if str.Value != "Hello World!" { + t.Errorf("String has wrong value. got=%q", str.Value) + } +} -- cgit v1.2.3