From 98b3baa1d9d05551948b6657b4130cf05c11934d Mon Sep 17 00:00:00 2001 From: Bobby Date: Thu, 4 Apr 2024 14:29:40 +0000 Subject: built in functions --- README.md | 45 +++++++++++++++++++++------------------------ evaluator/builtins.go | 23 +++++++++++++++++++++++ evaluator/evaluator.go | 28 ++++++++++++++++++---------- evaluator/evaluator_test.go | 32 ++++++++++++++++++++++++++++++++ object/object.go | 10 ++++++++++ 5 files changed, 104 insertions(+), 34 deletions(-) create mode 100644 evaluator/builtins.go diff --git a/README.md b/README.md index e6527cb..907ceac 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,8 @@ Mana is a toy programming language written in Go. It is a dynamically typed, int | `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 | +| `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 | | `HashLiteralExpression` | NYI | Hash Literal Expressions are used to represent hash values | `{"key": "value"}` | NYI | @@ -66,11 +67,6 @@ let add = fn(x, y) { return x + y; }; -// this is an alternative way to create a function -fn subtract(x, y) { - return 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) @@ -87,6 +83,7 @@ Mana is a dynamically typed language. This means that the type of a variable is | --------- | ----------------------- | ------- | | `Integer` | A 64-bit signed integer | `5` | | `Boolean` | A boolean value | `true` | +| `String` | A string value | `"foo"` | ## Operators @@ -136,6 +133,24 @@ fn add(x, y) { } ``` +## Built-in Functions + +Mana has a number of built-in functions that are available to the programmer. These functions are built into the language and can be used without having to define them. The following is a list of built-in functions that are available in Mana: + +| Function | Description | Parameters | Example | +| -------- | ------------------------------ | ---------- | ---------------------- | +| `len` | Returns the length of a string | `string` | `len("Hello, World!")` | + +## Strings + +Strings in Mana are enclosed in double quotes. Strings are immutable, which means that once a string is created, it cannot be changed. Strings can be concatenated using the `+` operator. + +```rust +let greeting = "Hello, "; +let name = "World!"; +let message = greeting + name; // message = "Hello, World!" +``` + ## 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: @@ -154,24 +169,6 @@ To run the tests, you can use the `go test` command. This will run all of the te go test ./... ``` -Or in a specific package: - -```bash -go test ./lexer -``` - -If you want to run a specific test, you can use the `-run` flag to specify a regular expression that matches the test names. - -```bash -go test -run TestLetStatement -``` - -Furthermore, you can use the `-v` flag to get verbose output from the tests. - -```bash -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/builtins.go b/evaluator/builtins.go new file mode 100644 index 0000000..0e6846a --- /dev/null +++ b/evaluator/builtins.go @@ -0,0 +1,23 @@ +package evaluator + +import ( + "mana/object" +) + +var builtins = map[string]*object.Builtin{ + "len": { + Fn: func(args ...object.Object) object.Object { + if len(args) != 1 { + return newError("wrong number of arguments. got=%d, want=1", + len(args)) + } + + switch arg := args[0].(type) { + case *object.String: + return &object.Integer{Value: int64(len(arg.Value))} + default: + return newError("argument to `len` not supported, got %s", arg.Type()) + } + }, + }, +} diff --git a/evaluator/evaluator.go b/evaluator/evaluator.go index 0eadd92..86d1889 100644 --- a/evaluator/evaluator.go +++ b/evaluator/evaluator.go @@ -98,15 +98,19 @@ func Eval(node ast.Node, env *object.Environment) object.Object { } func applyFunction(fn object.Object, args []object.Object) object.Object { - function, ok := fn.(*object.Function) + switch fn := fn.(type) { - if !ok { + case *object.Function: + extendedEnv := extendFunctionEnv(fn, args) + evaluated := Eval(fn.Body, extendedEnv) + return unwrapReturnValue(evaluated) + + case *object.Builtin: + return fn.Fn(args...) + + default: return newError("not a function: %s", fn.Type()) } - - extendedEnv := extendFunctionEnv(function, args) - evaulated := Eval(function.Body, extendedEnv) - return unwrapReturnValue(evaulated) } func extendFunctionEnv(fn *object.Function, args []object.Object) *object.Environment { @@ -288,11 +292,15 @@ func evalIfExpression(ie *ast.IfExpression, env *object.Environment) object.Obje } func evalIdentifier(node *ast.Identifier, env *object.Environment) object.Object { - val, ok := env.Get(node.Value) - if !ok { - return newError("identifier not found: " + node.Value) + if val, ok := env.Get(node.Value); ok { + return val } - return val + + if builtin, ok := builtins[node.Value]; ok { + return builtin + } + + return newError("identifier not found: " + node.Value) } func isTruthy(obj object.Object) bool { diff --git a/evaluator/evaluator_test.go b/evaluator/evaluator_test.go index 9738d9e..22fd80a 100644 --- a/evaluator/evaluator_test.go +++ b/evaluator/evaluator_test.go @@ -345,3 +345,35 @@ func TestStringConcatenation(t *testing.T) { t.Errorf("String has wrong value. got=%q", str.Value) } } + +func TestBuiltinFunctions(t *testing.T) { + tests := []struct { + input string + expected interface{} + }{ + {`len("")`, 0}, + {`len("four")`, 4}, + {`len("hello world")`, 11}, + {`len(1)`, "argument to `len` not supported, got INTEGER"}, + {`len("one", "two")`, "wrong number of arguments. got=2, want=1"}, + } + + for _, tt := range tests { + evaluated := testEval(tt.input) + + switch expected := tt.expected.(type) { + case int: + testIntegerObject(t, evaluated, int64(expected)) + case string: + errObj, ok := evaluated.(*object.Error) + if !ok { + t.Errorf("object is not Error. got=%T (%+v)", evaluated, evaluated) + continue + } + + if errObj.Message != expected { + t.Errorf("wrong error message. expected=%q, got=%q", expected, errObj.Message) + } + } + } +} diff --git a/object/object.go b/object/object.go index 731be0b..24de9be 100644 --- a/object/object.go +++ b/object/object.go @@ -17,6 +17,7 @@ const ( FUNCTION_OBJ = "FUNCTION" STRING_OBJ = "STRING" ERROR_OBJ = "ERROR" + BUILTIN_OBJ = "BUILTIN" ) type Object interface { @@ -52,6 +53,12 @@ type String struct { Value string } +type BuiltinFunction func(args ...Object) Object + +type Builtin struct { + Fn BuiltinFunction +} + func (i *Integer) Type() ObjectType { return INTEGER_OBJ } @@ -112,3 +119,6 @@ func (e *Error) Inspect() string { return "ERROR:" + e.Message } func (s *String) Type() ObjectType { return STRING_OBJ } func (s *String) Inspect() string { return s.Value } + +func (b *Builtin) Type() ObjectType { return BUILTIN_OBJ } +func (b *Builtin) Inspect() string { return "builtin function" } -- cgit v1.2.3