aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBobby <[email protected]>2024-04-04 14:29:40 +0000
committerBobby <[email protected]>2024-04-04 14:29:40 +0000
commit98b3baa1d9d05551948b6657b4130cf05c11934d (patch)
treec9ff7cc56f26e6481f0ca0cd74bae4f64abe53b0
parent2f28a8de055029c4501a8cfb796e7f4a1af8b719 (diff)
downloadmana-98b3baa1d9d05551948b6657b4130cf05c11934d.tar.xz
mana-98b3baa1d9d05551948b6657b4130cf05c11934d.zip
built in functions
-rw-r--r--README.md45
-rw-r--r--evaluator/builtins.go23
-rw-r--r--evaluator/evaluator.go28
-rw-r--r--evaluator/evaluator_test.go32
-rw-r--r--object/object.go10
5 files changed, 104 insertions, 34 deletions
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" }