aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md27
-rw-r--r--ast/ast.go20
-rw-r--r--evaluator/evaluator.go45
-rw-r--r--evaluator/evaluator_test.go79
-rw-r--r--object/object.go22
-rw-r--r--parser/parser.go64
-rw-r--r--parser/parser_test.go43
-rw-r--r--parser/parser_tracing.go54
8 files changed, 301 insertions, 53 deletions
diff --git a/README.md b/README.md
index 907ceac..fcc80f8 100644
--- a/README.md
+++ b/README.md
@@ -25,8 +25,8 @@ Mana is a toy programming language written in Go. It is a dynamically typed, int
| `CallExpression` | ✔️ | Call Expressions are used to call functions | `add(5, 5)` | ✔️ |
| `StringLiteralExpression` | ✔️ | String Literal Expressions are used to represent string values | `"Hello, World!"` | ✔️ |
| `BuiltInFunctions` | ✔️ | Built-in Functions are functions that are built into the language | `len("Hello, World!")` | ✔️ |
-| `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 |
+| `ArrayLiteralExpression` | ✔️ | Array Literal Expressions are used to represent array values | `[1, 2, 3]` | ✔️ |
+| `IndexExpression` | ✔️ | Index Expressions are used to index into arrays | `myArray[0]` | ✔️ |
| `HashLiteralExpression` | NYI | Hash Literal Expressions are used to represent hash values | `{"key": "value"}` | NYI |
\*_NYI = Not Yet Implemented_
@@ -151,6 +151,29 @@ let name = "World!";
let message = greeting + name; // message = "Hello, World!"
```
+## Arrays
+
+Arrays in Mana are ordered collections of values. Arrays are created using square brackets. Arrays can contain values of any type, including other arrays. Arrays are indexed using square brackets. The index is an integer value that represents the position of the element in the array. Arrays are zero-indexed, which means that the first element in the array is at index 0.
+
+```rust
+let numbers = [1, 2, 3, 4, 5];
+
+let first = numbers[0]; // first = 1
+
+let last = numbers[len(numbers) - 1]; // last = 5
+```
+
+```rust
+let mixed = [1, "two", true, fn(x) { x * x; }];
+let c = 69;
+
+let f = mixed[3](5); // f = 25
+
+let u = ["one", "two", 3][5 - 4]; // s = "two"
+
+let k = mixed[c - 67]; // k = true
+```
+
## Building the Project
To build the project, you will need to have Go installed on your machine. You can download Go from the [official website](https://golang.org/). Once you have Go installed, you can build the project by running the following command:
diff --git a/ast/ast.go b/ast/ast.go
index 9473f1a..cc30808 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -329,3 +329,23 @@ func (al *ArrayLiteral) String() string {
return out.String()
}
+
+type IndexExpression struct {
+ Token tokens.Token // the '[' token
+ Left Expression
+ Index Expression
+}
+
+func (ie *IndexExpression) expressionNode() {}
+func (ie *IndexExpression) TokenLiteral() string { return ie.Token.Literal }
+func (ie *IndexExpression) String() string {
+ var out bytes.Buffer
+
+ out.WriteString("(")
+ out.WriteString(ie.Left.String())
+ out.WriteString("[")
+ out.WriteString(ie.Index.String())
+ out.WriteString("])")
+
+ return out.String()
+}
diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go
index 86d1889..913138b 100644
--- a/evaluator/evaluator.go
+++ b/evaluator/evaluator.go
@@ -83,6 +83,30 @@ func Eval(node ast.Node, env *object.Environment) object.Object {
return &object.Function{Parameters: params, Body: body, Env: env}
+ case *ast.ArrayLiteral:
+ elements := evalExpressions(node.Elements, env)
+
+ if len(elements) == 1 && isError(elements[0]) {
+ return elements[0]
+ }
+
+ return &object.Array{Elements: elements}
+
+ case *ast.IndexExpression:
+ left := Eval(node.Left, env)
+
+ if isError(left) {
+ return left
+ }
+
+ index := Eval(node.Index, env)
+
+ if isError(index) {
+ return index
+ }
+
+ return evalIndexExpression(left, index)
+
case *ast.StringLiteral:
return &object.String{Value: node.Value}
@@ -291,6 +315,27 @@ func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Obje
}
}
+func evalIndexExpression(left, index object.Object) object.Object {
+ switch {
+ case left.Type() == object.ARRAY_OBJ && index.Type() == object.INTEGER_OBJ:
+ return evalArrayIndexExpression(left, index)
+ default:
+ return newError("index operator not supported: %s", left.Type())
+ }
+}
+
+func evalArrayIndexExpression(array, index object.Object) object.Object {
+ arrayObject := array.(*object.Array)
+ idx := index.(*object.Integer).Value
+ max := int64(len(arrayObject.Elements) - 1)
+
+ if idx < 0 || idx > max {
+ return NULL
+ }
+
+ return arrayObject.Elements[idx]
+}
+
func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object {
if val, ok := env.Get(node.Value); ok {
return val
diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go
index 22fd80a..3c72cda 100644
--- a/evaluator/evaluator_test.go
+++ b/evaluator/evaluator_test.go
@@ -377,3 +377,82 @@ func TestBuiltinFunctions(t *testing.T) {
}
}
}
+
+func TestArrayLiterals(t *testing.T) {
+ input := "[1, 2 * 2, 3 + 3]"
+
+ evaluated := testEval(input)
+
+ result, ok := evaluated.(*object.Array)
+
+ if !ok {
+ t.Fatalf("object is not Array. got=%T (%+v)", evaluated, evaluated)
+ }
+
+ if len(result.Elements) != 3 {
+ t.Fatalf("array has wrong num of elements. got=%d", len(result.Elements))
+ }
+
+ testIntegerObject(t, result.Elements[0], 1)
+ testIntegerObject(t, result.Elements[1], 4)
+ testIntegerObject(t, result.Elements[2], 6)
+}
+
+func TestArrayIndexExpressions(t *testing.T) {
+ tests := []struct {
+ input string
+ expected interface{}
+ }{
+ {
+ "[1, 2, 3][0]",
+ 1,
+ },
+ {
+ "[1, 2, 3][1]",
+ 2,
+ },
+ {
+ "[1, 2, 3][2]",
+ 3,
+ },
+ {
+ "let i = 0; [1][i];",
+ 1,
+ },
+ {
+ "[1, 2, 3][1 + 1];",
+ 3,
+ },
+ {
+ "let myArray = [1, 2, 3]; myArray[2];",
+ 3,
+ },
+ {
+ "let myArray = [1, 2, 3]; myArray[0] + myArray[1] + myArray[2];",
+ 6,
+ },
+ {
+ "let myArray = [1, 2, 3]; let i = myArray[0]; myArray[i]",
+ 2,
+ },
+ {
+ "[1, 2, 3][3]",
+ nil,
+ },
+ {
+ "[1, 2, 3][-1]",
+ nil,
+ },
+ }
+
+ for _, tt := range tests {
+ evaluated := testEval(tt.input)
+
+ switch expected := tt.expected.(type) {
+ case int:
+ testIntegerObject(t, evaluated, int64(expected))
+ case nil:
+ testNullObject(t, evaluated)
+ }
+ }
+}
diff --git a/object/object.go b/object/object.go
index 24de9be..9fe896e 100644
--- a/object/object.go
+++ b/object/object.go
@@ -18,6 +18,7 @@ const (
STRING_OBJ = "STRING"
ERROR_OBJ = "ERROR"
BUILTIN_OBJ = "BUILTIN"
+ ARRAY_OBJ = "ARRAY"
)
type Object interface {
@@ -59,6 +60,10 @@ type Builtin struct {
Fn BuiltinFunction
}
+type Array struct {
+ Elements []Object
+}
+
func (i *Integer) Type() ObjectType {
return INTEGER_OBJ
}
@@ -122,3 +127,20 @@ func (s *String) Inspect() string { return s.Value }
func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ }
func (b *Builtin) Inspect() string { return "builtin function" }
+
+func (ao *Array) Type() ObjectType { return ARRAY_OBJ }
+func (ao *Array) Inspect() string {
+ var out bytes.Buffer
+
+ elements := []string{}
+
+ for _, el := range ao.Elements {
+ elements = append(elements, el.Inspect())
+ }
+
+ out.WriteString("[")
+ out.WriteString(strings.Join(elements, ", "))
+ out.WriteString("]")
+
+ return out.String()
+}
diff --git a/parser/parser.go b/parser/parser.go
index a4472b2..4fa36b1 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -18,6 +18,7 @@ const (
PRODUCT // *
PREFIX // -X or !X
CALL // myFunction(X)
+ INDEX // array[index]
)
var precedences = map[tokens.TokenType]int{
@@ -30,6 +31,7 @@ var precedences = map[tokens.TokenType]int{
tokens.SLASH: PRODUCT,
tokens.ASTERISK: PRODUCT,
tokens.LPAREN: CALL,
+ tokens.LBRACKET: INDEX,
}
type (
@@ -85,6 +87,7 @@ func New(l *lexer.Lexer) *Parser {
p.registerInfix(tokens.LT, p.parseInfixExpression)
p.registerInfix(tokens.GT, p.parseInfixExpression)
p.registerInfix(tokens.LPAREN, p.parseCallExpression)
+ p.registerInfix(tokens.LBRACKET, p.parseIndexExpression)
return p
}
@@ -117,9 +120,9 @@ func (p *Parser) ParseProgram() *ast.Program {
// Parse the statement and append it to the program.
var stmt ast.Statement = p.parseStatement()
- if stmt != nil {
- program.Statements = append(program.Statements, stmt)
- }
+ // if stmt != nil {
+ program.Statements = append(program.Statements, stmt)
+ // }
// Advance to the next token.
p.nextToken()
@@ -334,9 +337,9 @@ func (p *Parser) parseBlockStatement() *ast.BlockStatement {
for !p.curTokenIs(tokens.RBRACE) && !p.curTokenIs(tokens.EOF) {
stmt := p.parseStatement()
- if stmt != nil {
- block.Statements = append(block.Statements, stmt)
- }
+ // if stmt != nil {
+ block.Statements = append(block.Statements, stmt)
+ // }
p.nextToken()
}
@@ -414,29 +417,29 @@ func (p *Parser) parseCallExpression(function ast.Expression) ast.Expression {
}
// parseCallArguments parses call arguments.
-func (p *Parser) parseCallArguments() []ast.Expression {
- args := []ast.Expression{}
+// func (p *Parser) parseCallArguments() []ast.Expression {
+// args := []ast.Expression{}
- if p.peekTokenIs(tokens.RPAREN) {
- p.nextToken()
- return args
- }
+// if p.peekTokenIs(tokens.RPAREN) {
+// p.nextToken()
+// return args
+// }
- p.nextToken()
- args = append(args, p.parseExpression(LOWEST))
+// p.nextToken()
+// args = append(args, p.parseExpression(LOWEST))
- for p.peekTokenIs(tokens.COMMA) {
- p.nextToken()
- p.nextToken()
- args = append(args, p.parseExpression(LOWEST))
- }
+// for p.peekTokenIs(tokens.COMMA) {
+// p.nextToken()
+// p.nextToken()
+// args = append(args, p.parseExpression(LOWEST))
+// }
- if !p.expectPeek(tokens.RPAREN) {
- return nil
- }
+// if !p.expectPeek(tokens.RPAREN) {
+// return nil
+// }
- return args
-}
+// return args
+// }
func (p *Parser) parseStringLiteral() ast.Expression {
return &ast.StringLiteral{
@@ -478,6 +481,19 @@ func (p *Parser) parseExpressionList(end tokens.TokenType) []ast.Expression {
return list
}
+func (p *Parser) parseIndexExpression(left ast.Expression) ast.Expression {
+ exp := &ast.IndexExpression{Token: p.curToken, Left: left}
+
+ p.nextToken()
+ exp.Index = p.parseExpression(LOWEST)
+
+ if !p.expectPeek(tokens.RBRACKET) {
+ return nil
+ }
+
+ return exp
+}
+
// curTokenIs returns true if the current token is of the given type.
func (p *Parser) curTokenIs(t tokens.TokenType) bool {
return p.curToken.Type == t
diff --git a/parser/parser_test.go b/parser/parser_test.go
index f1b8b8c..7b347f5 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -519,6 +519,14 @@ func TestOperatorPrecedenceParsing(t *testing.T) {
"add(a + b + c * d / f + g)",
"add((((a + b) + ((c * d) / f)) + g))",
},
+ {
+ "a * [1, 2, 3, 4][b * c] * d",
+ "((a * ([1, 2, 3, 4][(b * c)])) * d)",
+ },
+ {
+ "add(a * b[2], b[1], 2 * [1, 2][1])",
+ "add((a * (b[2])), (b[1]), (2 * ([1, 2][1])))",
+ },
}
for _, tt := range tests {
@@ -802,6 +810,11 @@ func TestParsingArrayLiterals(t *testing.T) {
checkParserErrors(t, p)
stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
+
+ if !ok {
+ t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", program.Statements[0])
+ }
+
array, ok := stmt.Expression.(*ast.ArrayLiteral)
if !ok {
@@ -816,3 +829,33 @@ func TestParsingArrayLiterals(t *testing.T) {
testInfixExpression(t, array.Elements[1], 2, "*", 2)
testInfixExpression(t, array.Elements[2], 3, "+", 3)
}
+
+func TestParsingIndexExpressions(t *testing.T) {
+ input := "myArray[1 + 1]"
+
+ var l *lexer.Lexer = lexer.New(input)
+ var p *Parser = New(l)
+
+ var program *ast.Program = p.ParseProgram()
+ checkParserErrors(t, p)
+
+ stmt, ok := program.Statements[0].(*ast.ExpressionStatement)
+
+ if !ok {
+ t.Fatalf("program.Statements[0] is not ast.ExpressionStatement. got=%T", program.Statements[0])
+ }
+
+ indexExp, ok := stmt.Expression.(*ast.IndexExpression)
+
+ if !ok {
+ t.Fatalf("exp is not ast.IndexExpression. got=%T", stmt.Expression)
+ }
+
+ if !testIdentifier(t, indexExp.Left, "myArray") {
+ return
+ }
+
+ if !testInfixExpression(t, indexExp.Index, 1, "+", 1) {
+ return
+ }
+}
diff --git a/parser/parser_tracing.go b/parser/parser_tracing.go
index 2f3fcdb..76a8c45 100644
--- a/parser/parser_tracing.go
+++ b/parser/parser_tracing.go
@@ -1,37 +1,37 @@
package parser
-import (
- "fmt"
- "strings"
-)
+// import (
+// "fmt"
+// "strings"
+// )
-var traceLevel int = 0
+// var traceLevel int = 0
-const traceIdentPlaceholder string = "\t"
+// const traceIdentPlaceholder string = "\t"
-func identLevel() string {
- return strings.Repeat(traceIdentPlaceholder, traceLevel-1)
-}
+// func identLevel() string {
+// return strings.Repeat(traceIdentPlaceholder, traceLevel-1)
+// }
-func tracePrint(fs string) {
- fmt.Printf("%s%s\n", identLevel(), fs)
-}
+// func tracePrint(fs string) {
+// fmt.Printf("%s%s\n", identLevel(), fs)
+// }
-func incIdent() {
- traceLevel = traceLevel + 1
-}
+// func incIdent() {
+// traceLevel = traceLevel + 1
+// }
-func decIdent() {
- traceLevel = traceLevel - 1
-}
+// func decIdent() {
+// traceLevel = traceLevel - 1
+// }
-func trace(msg string) string {
- incIdent()
- tracePrint("BEGIN " + msg)
- return msg
-}
+// func trace(msg string) string {
+// incIdent()
+// tracePrint("BEGIN " + msg)
+// return msg
+// }
-func untrace(msg string) {
- tracePrint("END " + msg)
- decIdent()
-}
+// func untrace(msg string) {
+// tracePrint("END " + msg)
+// decIdent()
+// }